基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

NO IMAGE

一、集群簡介

1.1 集群架構

當單臺 RabbitMQ 服務器的處理消息的能力達到瓶頸時,此時可以通過 RabbitMQ 集群來進行擴展,從而達到提升吞吐量的目的。RabbitMQ 集群是一個或多個節點的邏輯分組,集群中的每個節點都是對等的,每個節點共享所有的用戶,虛擬主機,隊列,交換器,綁定關係,運行時參數和其他分佈式狀態等信息。一個高可用,負載均衡的 RabbitMQ 集群架構應類似下圖:

基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

這裡對上面的集群架構做一下解釋說明:

首先一個基本的 RabbitMQ 集群不是高可用的,雖然集群共享隊列,但在默認情況下,消息只會被路由到某一個節點的符合條件的隊列上,並不會同步到其他節點的相同隊列上。假設消息路由到 node1 的 my-queue 隊列上,但是 node1 突然宕機了,那麼消息就會丟失,想要解決這個問題,需要開啟隊列鏡像,將集群中的隊列彼此之間進行鏡像,此時消息就會被拷貝到處於同一個鏡像分組中的所有隊列上。

其次 RabbitMQ 集群本身並沒有提供負載均衡的功能,也就是說對於一個三節點的集群,每個節點的負載可能都是不相同的,想要解決這個問題可以通過硬件負載均衡或者軟件負載均衡的方式,這裡我們選擇使用 HAProxy 來進行負載均衡,當然也可以使用其他負載均衡中間件,如 LVS 等。HAProxy 同時支持四層和七層負載均衡,並基於單一進程的事件驅動模型,因此它可以支持非常高的井發連接數。

接著假設我們只採用一臺 HAProxy ,那麼它就存在明顯的單點故障的問題,所以至少需要兩臺 HAProxy ,同時這兩臺 HAProxy 之間需要能夠自動進行故障轉移,通常的解決方案就是 KeepAlived 。KeepAlived 採用 VRRP (Virtual Router Redundancy Protocol,虛擬路由冗餘協議) 來解決單點失效的問題,它通常由一組一備兩個節點組成,同一時間內只有主節點會提供對外服務,並同時提供一個虛擬的 IP 地址 (Virtual Internet Protocol Address ,簡稱 VIP) 。 如果主節點故障,那麼備份節點會自動接管 VIP 併成為新的主節點 ,直到原有的主節點恢復。

最後,任何想要連接到 RabbitMQ 集群的客戶端只需要連接到虛擬 IP,而不必關心集群是何種架構,示例如下:

ConnectionFactory factory = new ConnectionFactory();
// 假設虛擬ip為 192.168.0.200
factory.setHost("192.168.0.200");

1.2 部署情況

下面我們開始進行搭建,這裡我使用三臺主機進行演示,主機名分別為 hadoop001,002 和 003 ,其功能分配如下:

  • hadoop001 服務器:部署 RabbitMQ + HAProxy + KeepAlived ;
  • hadoop002 服務器:部署 RabbitMQ + HAProxy + KeepAlived ;
  • hadoop003 服務器:部署 RabbitMQ

以上三臺主機上我均已安裝好了 RabbitMQ ,關於 RabbitMQ 的安裝步驟可以參考:RabbitMQ單機環境搭建

二、RabbitMQ 集群搭建

首先先進行 RabbitMQ 集群的搭建,具體步驟如下:

2.1 拷貝 cookie

將 hadoop001 上的 .erlang.cookie 文件拷貝到其他兩臺主機上。該 cookie 文件相當於密鑰令牌,集群中的 RabbitMQ 節點需要通過交換密鑰令牌以獲得相互認證,因此處於同一集群的所有節點需要具有相同的密鑰令牌,否則在搭建過程中會出現 Authentication Fail 錯誤。

RabbitMQ 服務啟動時,erlang VM 會自動創建該 cookie 文件,默認的存儲路徑為 /var/lib/rabbitmq/.erlang.cookie$HOME/.erlang.cookie,該文件是一個隱藏文件,需要使用 ls -al 命令查看。這裡我使用的是 root 賬戶,$HOME 目錄就是 /root 目錄,對應的拷貝命令如下:

scp /root/.erlang.cookie [email protected]:/root/
scp /root/.erlang.cookie [email protected]:/root/

由於你可能在三臺主機上使用不同的賬戶進行操作,為避免後面出現權限不足的問題,這裡建議將 cookie 文件原來的 400 權限改為 777,命令如下:

chmod  777 /root/.erlang.cookie

注:cookie 中的內容就是一行隨機字符串,可以使用 cat 命令查看。

2.2 啟動服務

在三臺主機上均執行以下命令,啟動 RabbitMQ 服務:

rabbitmq-server start -detached

這裡預先進行一下說明:該命令會同時啟動 Erlang 虛擬機和 RabbitMQ 應用服務。而後文用到的 rabbitmqctl start_app 只會啟動 RabbitMQ 應用服務, rabbitmqctl stop_app 只會停止 RabbitMQ 服務。

2.3 集群搭建

RabbitMQ 集群的搭建需要選擇其中任意一個節點為基準,將其它節點逐步加入。這裡我們以 hadoop001 為基準節點,將 hadoop002 和 hadoop003 加入集群。在 hadoop002 和 hadoop003 上執行以下命令:

# 1.停止服務
rabbitmqctl stop_app
# 2.重置狀態
rabbitmqctl reset
# 3.節點加入
rabbitmqctl join_cluster --ram [email protected]
# 4.啟動服務
rabbitmqctl start_app

join_cluster 命令有一個可選的參數 --ram ,該參數代表新加入的節點是內存節點,默認是磁盤節點。如果是內存節點,則所有的隊列、交換器、綁定關係、用戶、訪問權限和 vhost 的元數據都將存儲在內存中,如果是磁盤節點,則存儲在磁盤中。內存節點可以有更高的性能,但其重啟後所有配置信息都會丟失,因此RabbitMQ 要求在集群中至少有一個磁盤節點,其他節點可以是內存節點。當內存節點離開集群時,它可以將變更通知到至少一個磁盤節點;然後在其重啟時,再連接到磁盤節點上獲取元數據信息。除非是將 RabbitMQ 用於 RPC 這種需要超低延遲的場景,否則在大多數情況下,RabbitMQ 的性能都是夠用的,可以採用默認的磁盤節點的形式。這裡為了演示,hadoop002 我就設置為內存節點。

另外,如果節點以磁盤節點的形式加入,則需要先使用 reset 命令進行重置,然後才能加入現有群集,重置節點會刪除該節點上存在的所有的歷史資源和數據。採用內存節點的形式加入時可以略過 reset 這一步,因為內存上的數據本身就不是持久化的。

2.4 查看集群狀態

1. 命令行查看

在 hadoop002 和 003 上執行以上命令後,集群就已經搭建成功,此時可以在任意節點上使用 rabbitmqctl cluster_status 命令查看集群狀態,輸出如下:

[[email protected] ~]# rabbitmqctl cluster_status
Cluster status of node [email protected] ...
[{nodes,[{disc,[[email protected],[email protected]]},{ram,[[email protected]]}]},
{running_nodes,[[email protected],[email protected],[email protected]]},
{cluster_name,<<"[email protected]">>},
{partitions,[]},
{alarms,[{[email protected],[]},{[email protected],[]},{[email protected],[]}]}]

可以看到 nodes 下顯示了全部節點的信息,其中 hadoop001 和 hadoop003 上的節點都是 disc 類型,即磁盤節點;而 hadoop002 上的節點為 ram,即內存節點。此時代表集群已經搭建成功,默認的 cluster_name 名字為 [email protected],如果你想進行修改,可以使用以下命令:

rabbitmqctl set_cluster_name my_rabbitmq_cluster

2. UI 界面查看

除了可以使用命令行外,還可以使用打開任意節點的 UI 界面進行查看,情況如下:

基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

2.5 配置鏡像隊列

1. 開啟鏡像隊列

這裡我們為所有隊列開啟鏡像配置,其語法如下:

rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}'

2. 複製係數

在上面我們指定了 ha-mode 的值為 all ,代表消息會被同步到所有節點的相同隊列中。這裡我們之所以這樣配置,因為我們本身只有三個節點,因此複製操作的性能開銷比較小。如果你的集群有很多節點,那麼此時複製的性能開銷就比較大,此時需要選擇合適的複製係數。通常可以遵循過半寫原則,即對於一個節點數為 n 的集群,只需要同步到 n/2+1 個節點上即可。此時需要同時修改鏡像策略為 exactly,並指定複製係數 ha-params,示例命令如下:

rabbitmqctl set_policy ha-two "^" '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

除此之外,RabbitMQ 還支持使用正則表達式來過濾需要進行鏡像操作的隊列,示例如下:

rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'

此時只會對 ha 開頭的隊列進行鏡像。更多鏡像隊列的配置說明,可以參考官方文檔:Highly Available (Mirrored) Queues

3. 查看鏡像狀態

配置完成後,可以通過 Web UI 界面查看任意隊列的鏡像狀態,情況如下:

基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

2.6 節點下線

以上介紹的集群搭建的過程就是服務擴容的過程,如果想要進行服務縮容,即想要把某個節點剔除集群,有兩種可選方式:

第一種:可以先使用 rabbitmqctl stop 停止該節點上的服務,然後在其他任意一個節點上執行 forget_cluster_node 命令。這裡以剔除 hadoop003 上的服務為例,此時可以在 hadoop001 或 002 上執行下面的命令:

rabbitmqctl forget_cluster_node [email protected]

第二種方式:先使用 rabbitmqctl stop 停止該節點上的服務,然後再執行 rabbitmqctl reset 這會清空該節點上所有歷史數據,並主動通知集群中其它節點它將要離開集群。

2.7 集群的關閉與重啟

沒有一個直接的命令可以關閉整個集群,需要逐一進行關閉。但是需要保證在重啟時,最後關閉的節點最先被啟動。如果第一個啟動的不是最後關閉的節點,那麼這個節點會等待最後關閉的那個節點啟動,默認進行 10 次連接嘗試,超時時間為 30 秒,如果依然沒有等到,則該節點啟動失敗。

這帶來的一個問題是,假設在一個三節點的集群當中,關閉的順序為 node1,node2,node3,如果 node1 因為故障暫時沒法恢復,此時 node2 和 node3 就無法啟動。想要解決這個問題,可以先將 node1 節點進行剔除,命令如下:

rabbitmqctl forget_cluster_node [email protected] -offline

此時需要加上 -offline 參數,它允許節點在自身沒有啟動的情況下將其他節點剔除。

三、HAProxy 環境搭建

3.1 下載

HAProxy 官方下載地址為:www.haproxy.org/#down ,如果這個網站無法訪問,也可以從 src.fedoraproject.org/repo/pkgs/h… 上進行下載。這裡我下載的是 2.x 的版本,下載後進行解壓:

tar -zxvf haproxy-2.0.3.tar.gz

3.2 編譯

進入解壓後根目錄,執行下面的編譯命令:

make TARGET=linux-glibc  PREFIX=/usr/app/haproxy-2.0.3
make install PREFIX=/usr/app/haproxy-2.0.3

3.3 配置環境變量

配置環境變量:

vim /etc/profile
export HAPROXY_HOME=/usr/app/haproxy-2.0.3
export PATH=$PATH:$HAPROXY_HOME/sbin

使得配置的環境變量立即生效:

source /etc/profile

3.4 負載均衡配置

新建配置文件 haproxy.cfg,這裡我新建的位置為:/etc/haproxy/haproxy.cfg,文件內容如下:

# 全局配置
global
# 日誌輸出配置、所有日誌都記錄在本機,通過 local0 進行輸出
log 127.0.0.1 local0 info
# 最大連接數
maxconn 4096
# 改變當前的工作目錄
chroot /usr/app/haproxy-2.0.3
# 以指定的 UID 運行 haproxy 進程
uid 99
# 以指定的 GID 運行 haproxy 進程
gid 99
# 以守護進行的方式運行
daemon
# 當前進程的 pid 文件存放位置
pidfile /usr/app/haproxy-2.0.3/haproxy.pid
# 默認配置
defaults
# 應用全局的日誌配置
log global
# 使用4層代理模式,7層代理模式則為"http"
mode tcp
# 日誌類別
option tcplog
# 不記錄健康檢查的日誌信息
option dontlognull
# 3次失敗則認為服務不可用
retries 3
# 每個進程可用的最大連接數
maxconn 2000
# 連接超時
timeout connect 5s
# 客戶端超時
timeout client 120s
# 服務端超時
timeout server 120s
# 綁定配置
listen rabbitmq_cluster
bind :5671
# 配置TCP模式
mode tcp
# 採用加權輪詢的機制進行負載均衡
balance roundrobin
# RabbitMQ 集群節點配置
server node1 hadoop001:5672 check inter 5000 rise 2 fall 3 weight 1
server node2 hadoop002:5672 check inter 5000 rise 2 fall 3 weight 1
server node3 hadoop003:5672 check inter 5000 rise 2 fall 3 weight 1
# 配置監控頁面
listen monitor
bind :8100
mode http
option httplog
stats enable
stats uri /stats
stats refresh 5s

負載均衡的主要配置在 listen rabbitmq_cluster 下,這裡指定負載均衡的方式為加權輪詢,同時定義好健康檢查機制:

server node1 hadoop001:5672 check inter 5000 rise 2 fall 3 weight 1

以上配置代表對地址為 hadoop001:5672 的 node1 節點每隔 5 秒進行一次健康檢查,如果連續兩次的檢查結果都是正常,則認為該節點可用,此時可以將客戶端的請求輪詢到該節點上;如果連續 3 次的檢查結果都不正常,則認為該節點不可用。weight 用於指定節點在輪詢過程中的權重。

3.5 啟動服務

以上搭建步驟在 hadoop001 和 hadoop002 上完全相同,搭建完成使用以下命令啟動服務:

haproxy -f /etc/haproxy/haproxy.cfg

啟動後可以在監控頁面進行查看,端口為設置的 8100,完整地址為:http://hadoop001:8100/stats ,頁面情況如下:

基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

所有節點都為綠色,代表節點健康。此時證明 HAProxy 搭建成功,並已經對 RabbitMQ 集群進行監控。

四、KeepAlived 環境搭建

接著就可以搭建 Keepalived 來解決 HAProxy 故障轉移的問題。這裡我在 hadoop001 和 hadoop002 上安裝 KeepAlived ,兩臺主機上的搭建的步驟完全相同,只是部分配置略有不同,具體如下:

4.1 下載

直接從 Keepalived 官方下載所需版本,這裡我下載的為 2.x 的版本。下載後進行解壓:

wget https://www.keepalived.org/software/keepalived-2.0.18.tar.gz
tar -zxvf keepalived-2.0.18.tar.gz

4.2 編譯

安裝相關依賴後進行編譯:

# 安裝依賴
yum -y install libnl libnl-devel
# 編譯安裝
./configure --prefix=/usr/app/keepalived-2.0.18
make && make install

4.3 環境配置

由於不是採用 yum 的方式進行安裝,而是採用壓縮包的方式進行安裝,此時需要進行環境配置,具體如下:

Keepalived 默認會從 /etc/keepalived/keepalived.conf 路徑讀取配置文件,所以需要將安裝後的配置文件拷貝到該路徑:

mkdir /etc/keepalived
cp /usr/app/keepalived-2.0.18/etc/keepalived/keepalived.conf /etc/keepalived/

將所有 Keepalived 腳本拷貝到 /etc/init.d/ 目錄下:

# 編譯目錄中的腳本
cp /usr/software/keepalived-2.0.18/keepalived/etc/init.d/keepalived /etc/init.d/
# 安裝目錄中的腳本
cp /usr/app/keepalived-2.0.18/etc/sysconfig/keepalived /etc/sysconfig/
cp /usr/app/keepalived-2.0.18/sbin/keepalived /usr/sbin/

設置開機自啟動:

chmod +x /etc/init.d/keepalived
chkconfig --add keepalived
systemctl enable keepalived.service

4.4 配置 HAProxy 檢查

這裡先對 hadoop001 上 keepalived.conf 配置文件進行修改,完整內容如下:

global_defs {
# 路由id,主備節點不能相同
router_id node1
}
# 自定義監控腳本
vrrp_script chk_haproxy {
# 腳本位置
script "/etc/keepalived/haproxy_check.sh" 
# 腳本執行的時間間隔
interval 5 
weight 10
}
vrrp_instance VI_1 {
# Keepalived的角色,MASTER 表示主節點,BACKUP 表示備份節點
state MASTER  
# 指定監測的網卡,可以使用 ifconfig 進行查看
interface enp0s8
# 虛擬路由的id,主備節點需要設置為相同
virtual_router_id 1
# 優先級,主節點的優先級需要設置比備份節點高
priority 100 
# 設置主備之間的檢查時間,單位為秒 
advert_int 1 
# 定義驗證類型和密碼
authentication { 
auth_type PASS
auth_pass 123456
}
# 調用上面自定義的監控腳本
track_script {
chk_haproxy
}
virtual_ipaddress {
# 虛擬IP地址,可以設置多個
192.168.0.200  
}
}

以上配置定義了 hadoop001上的 Keepalived 節點為 MASTER 節點,並設置對外提供服務的虛擬 IP 為 192.168.0.200。此外最主要的是定義了通過 haproxy_check.sh 來對 HAProxy 進行監控,這個腳本需要我們自行創建,內容如下:

#!/bin/bash
# 判斷haproxy是否已經啟動
if [ ${ps -C haproxy --no-header |wc -l} -eq 0 ] ; then
#如果沒有啟動,則啟動
haproxy -f /etc/haproxy/haproxy.cfg
fi
#睡眠3秒以便haproxy完全啟動
sleep 3
#如果haproxy還是沒有啟動,此時需要將本機的keepalived服務停掉,以便讓VIP自動漂移到另外一臺haproxy
if [ ${ps -C haproxy --no-header |wc -l} -eq 0 ] ; then
systemctl stop keepalived
fi

創建後為其賦予執行權限:

chmod +x /etc/keepalived/haproxy_check.sh

這個腳本主要用於判斷 HAProxy 服務是否正常,如果不正常且無法啟動,此時就需要將本機 Keepalived 關閉,從而讓虛擬 IP 漂移到備份節點。備份節點的配置與主節點基本相同,但是需要修改其 state 為 BACKUP;同時其優先級 priority 需要比主節點低。完整配置如下:

global_defs {
# 路由id,主備節點不能相同    
router_id node2  
}
vrrp_script chk_haproxy {
script "/etc/keepalived/haproxy_check.sh" 
interval 5 
weight 10
}
vrrp_instance VI_1 {
# BACKUP 表示備份節點
state BACKUP 
interface enp0s8
virtual_router_id 1
# 優先級,備份節點要比主節點低
priority 50 
advert_int 1 
authentication { 
auth_type PASS
auth_pass 123456
}
track_script {
chk_haproxy
}
virtual_ipaddress {
192.168.0.200  
}
}

4.5 啟動服務

分別在 hadoop001 和 hadoop002 上啟動 KeepAlived 服務,命令如下:

systemctl start  keepalived

啟動後此時 hadoop001 為主節點,可以在 hadoop001 上使用 ip a 命令查看到虛擬 IP 的情況:

基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

此時只有 hadoop001 上是存在虛擬 IP 的,而 hadoop002 上是沒有的。

基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

4.6 驗證故障轉移

這裡我們驗證一下故障轉移,因為按照我們上面的檢測腳本,如果 HAProxy 已經停止且無法重啟時 KeepAlived 服務就會停止,這裡我們直接使用以下命令停止 Keepalived 服務:

systemctl stop keepalived

此時再次使用 ip a 分別查看,可以發現 hadoop001 上的 VIP 已經漂移到 hadoop002 上,情況如下:

基於HAProxy+KeepAlived搭建RabbitMQ高可用集群

此時對外服務的 VIP 依然可用,代表已經成功地進行了故障轉移。至此集群已經搭建成功,任何需要發送或者接受消息的客戶端服務只需要連接到該 VIP 即可,示例如下:

ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.0.200");

參考資料

  1. 朱忠華 . RabbitMQ實戰指南 . 電子工業出版社 . 2017-11-1
  2. RabbitMQ 官方文檔 —— 集群指南:www.rabbitmq.com/clustering.…
  3. RabbitMQ 官方文檔 —— 高可用鏡像隊列:www.rabbitmq.com/ha.html
  4. HAProxy 官方配置手冊:cbonte.github.io/haproxy-dco…
  5. KeepAlived 官方配置手冊:www.keepalived.org/manpage.htm…

更多文章,歡迎訪問 [全棧工程師手冊] ,GitHub 地址:github.com/heibaiying/…

相關文章

「萬字整理」這裡有一份Node.js入門指南和實踐,請注意查收❤️

【金三銀四】Redis面試熱點之底層實現篇

11道JS選擇題,聽說第一題就難倒80%的人

MacOS10.15:修正typora無法輸入問題