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

heibaiying發表於2020-01-06

一、叢集簡介

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 root@hadoop002:/root/
scp /root/.erlang.cookie root@hadoop003:/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 rabbit@hadoop001
# 4.啟動服務
rabbitmqctl start_app
複製程式碼

join_cluster 命令有一個可選的引數 --ram ,該引數代表新加入的節點是記憶體節點,預設是磁碟節點。如果是記憶體節點,則所有的佇列、交換器、繫結關係、使用者、訪問許可權和 vhost 的後設資料都將儲存在記憶體中,如果是磁碟節點,則儲存在磁碟中。記憶體節點可以有更高的效能,但其重啟後所有配置資訊都會丟失,因此RabbitMQ 要求在叢集中至少有一個磁碟節點,其他節點可以是記憶體節點。當記憶體節點離開叢集時,它可以將變更通知到至少一個磁碟節點;然後在其重啟時,再連線到磁碟節點上獲取後設資料資訊。除非是將 RabbitMQ 用於 RPC 這種需要超低延遲的場景,否則在大多數情況下,RabbitMQ 的效能都是夠用的,可以採用預設的磁碟節點的形式。這裡為了演示,hadoop002 我就設定為記憶體節點。

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

2.4 檢視叢集狀態

1. 命令列檢視

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

[root@hadoop001 ~]# rabbitmqctl cluster_status
Cluster status of node rabbit@hadoop001 ...
[{nodes,[{disc,[rabbit@hadoop001,rabbit@hadoop003]},{ram,[rabbit@hadoop002]}]},
{running_nodes,[rabbit@hadoop003,rabbit@hadoop002,rabbit@hadoop001]},
{cluster_name,<<"rabbit@hadoop001">>},
{partitions,[]},
{alarms,[{rabbit@hadoop003,[]},{rabbit@hadoop002,[]},{rabbit@hadoop001,[]}]}]
複製程式碼

可以看到 nodes 下顯示了全部節點的資訊,其中 hadoop001 和 hadoop003 上的節點都是 disc 型別,即磁碟節點;而 hadoop002 上的節點為 ram,即記憶體節點。此時代表叢集已經搭建成功,預設的 cluster_name 名字為 rabbit@hadoop001,如果你想進行修改,可以使用以下命令:

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 rabbit@hadoop003
複製程式碼

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

2.7 叢集的關閉與重啟

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

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

rabbitmqctl forget_cluster_node rabbit@node1 -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/…

相關文章