一、叢集簡介
1.1 叢集架構
當單臺 RabbitMQ 伺服器的處理訊息的能力達到瓶頸時,此時可以通過 RabbitMQ 叢集來進行擴充套件,從而達到提升吞吐量的目的。RabbitMQ 叢集是一個或多個節點的邏輯分組,叢集中的每個節點都是對等的,每個節點共享所有的使用者,虛擬主機,佇列,交換器,繫結關係,執行時引數和其他分散式狀態等資訊。一個高可用,負載均衡的 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 介面進行檢視,情況如下:
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 介面檢視任意佇列的映象狀態,情況如下:
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 搭建成功,並已經對 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 的情況:
此時只有 hadoop001 上是存在虛擬 IP 的,而 hadoop002 上是沒有的。
4.6 驗證故障轉移
這裡我們驗證一下故障轉移,因為按照我們上面的檢測指令碼,如果 HAProxy 已經停止且無法重啟時 KeepAlived 服務就會停止,這裡我們直接使用以下命令停止 Keepalived 服務:
systemctl stop keepalived
複製程式碼
此時再次使用 ip a
分別檢視,可以發現 hadoop001 上的 VIP 已經漂移到 hadoop002 上,情況如下:
此時對外服務的 VIP 依然可用,代表已經成功地進行了故障轉移。至此叢集已經搭建成功,任何需要傳送或者接受訊息的客戶端服務只需要連線到該 VIP 即可,示例如下:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.0.200");
複製程式碼
參考資料
- 朱忠華 . RabbitMQ實戰指南 . 電子工業出版社 . 2017-11-1
- RabbitMQ 官方文件 —— 叢集指南:www.rabbitmq.com/clustering.…
- RabbitMQ 官方文件 —— 高可用映象佇列:www.rabbitmq.com/ha.html
- HAProxy 官方配置手冊:cbonte.github.io/haproxy-dco…
- KeepAlived 官方配置手冊:www.keepalived.org/manpage.htm…
更多文章,歡迎訪問 [全棧工程師手冊] ,GitHub 地址:github.com/heibaiying/…