點選上方“芋道原始碼”,選擇“置頂公眾號”
技術文章第一時間送達!
原始碼精品專欄
一般來說,如果只是為了學習RabbitMQ或者驗證業務工程的正確性那麼在本地環境或者測試環境上使用其單實體部署就可以了,但是出於MQ中介軟體本身的可靠性、併發性、吞吐量和訊息堆積能力等問題的考慮,在生產環境上一般都會考慮使用RabbitMQ的叢集方案。
對於RabbitMQ這麼成熟的訊息佇列產品來說,搭建它並不難並且也有不少童鞋寫過如何搭建RabbitMQ訊息佇列叢集的博文,但可能仍然有童鞋並不瞭解其背後的原理,這會導致其遇到效能問題時無法對叢集進行進一步的調優。本篇主要介紹RabbitMQ叢集方案的原理,如何搭建具備負載均衡能力的中小規模RabbitMQ叢集,並最後給出生產環境構建一個能夠具備高可用、高可靠和高吞吐量的中小規模RabbitMQ叢集設計方案。
一、RabbitMQ叢集方案的原理
RabbitMQ這款訊息佇列中介軟體產品本身是基於Erlang編寫,Erlang語言天生具備分散式特性(透過同步Erlang叢集各節點的magic cookie來實現)。因此,RabbitMQ天然支援Clustering。這使得RabbitMQ本身不需要像ActiveMQ、Kafka那樣透過ZooKeeper分別來實現HA方案和儲存叢集的元資料。叢集是保證可靠性的一種方式,同時可以透過水平擴充套件以達到增加訊息吞吐量能力的目的。下麵先來看下RabbitMQ叢集的整體方案:上面圖中採用三個節點組成了一個RabbitMQ的叢集,Exchange A(交換器,對於RabbitMQ基礎概念不太明白的童鞋可以看下基礎概念)的元資料資訊在所有節點上是一致的,而Queue(存放訊息的佇列)的完整資料則只會存在於它所建立的那個節點上。,其他節點只知道這個queue的metadata資訊和一個指向queue的owner node的指標。
(1)RabbitMQ叢集元資料的同步
RabbitMQ叢集會始終同步四種型別的內部元資料(類似索引):
a.佇列元資料:佇列名稱和它的屬性;
b.交換器元資料:交換器名稱、型別和屬性;
c.系結元資料:一張簡單的表格展示瞭如何將訊息路由到佇列;
d.vhost元資料:為vhost內的佇列、交換器和系結提供名稱空間和安全屬性;
因此,當使用者訪問其中任何一個RabbitMQ節點時,透過rabbitmqctl查詢到的queue/user/exchange/vhost等資訊都是相同的。
(2)為何RabbitMQ叢集僅採用元資料同步的方式
我想肯定有不少同學會問,想要實現HA方案,那將RabbitMQ叢集中的所有Queue的完整資料在所有節點上都儲存一份不就可以了麼?(可以類似MySQL的主主樣式嘛)這樣子,任何一個節點出現故障或者宕機不可用時,那麼使用者的客戶端只要能連線至其他節點能夠照常完成訊息的釋出和訂閱嘛。
我想RabbitMQ的作者這麼設計主要還是基於叢集本身的效能和儲存空間上來考慮。第一,儲存空間,如果每個叢集節點都擁有所有Queue的完全資料複製,那麼每個節點的儲存空間會非常大,叢集的訊息積壓能力會非常弱(無法透過叢集節點的擴容提高訊息積壓能力);第二,效能,訊息的釋出者需要將訊息複製到每一個叢集節點,對於持久化訊息,網路和磁碟同步複製的開銷都會明顯增加。
(3)RabbitMQ叢集傳送/訂閱訊息的基本原理
RabbitMQ叢集的工作原理圖如下:
場景1、客戶端直接連線佇列所在節點
如果有一個訊息生產者或者訊息消費者透過amqp-client的客戶端連線至節點1進行訊息的釋出或者訂閱,那麼此時的叢集中的訊息收發只與節點1相關,這個沒有任何問題;如果客戶端相連的是節點2或者節點3(佇列1資料不在該節點上),那麼情況又會是怎麼樣呢?
場景2、客戶端連線的是非佇列資料所在節點
如果訊息生產者所連線的是節點2或者節點3,此時佇列1的完整資料不在該兩個節點上,那麼在傳送訊息過程中這兩個節點主要起了一個路由轉發作用,根據這兩個節點上的元資料(也就是上文提到的:指向queue的owner node的指標)轉發至節點1上,最終傳送的訊息還是會儲存至節點1的佇列1上。
同樣,如果訊息消費者所連線的節點2或者節點3,那這兩個節點也會作為路由節點起到轉發作用,將會從節點1的佇列1中拉取訊息進行消費。
二、RabbitMQ叢集的搭建
(1)搭建RabbitMQ叢集所需要安裝的元件
在搭建RabbitMQ叢集之前有必要在每臺虛擬機器上安裝如下的元件包,分別如下:
a.Jdk 1.8
b.Erlang執行時環境,這裡用的是otpsrc19.3.tar.gz (200MB+)
c.RabbitMq的Server元件,這裡用的rabbitmq-server-generic-unix-3.6.10.tar.gz
關於如何安裝上述三個元件的具體步驟,已經有不少博文對此進行了非常詳細的描述,那麼本文就不再贅述了。有需要的同學可以具體參考這些步驟來完成安裝。
(2)搭建10節點組成的RabbitMQ叢集
該節中主要展示的是叢集搭建,需要確保每臺機器上正確安裝了上述三種元件,並且每臺虛擬機器上的RabbitMQ的實體能夠正常啟動起來。
a.編輯每臺RabbitMQ的cookie檔案,以確保各個節點的cookie檔案使用的是同一個值,可以scp其中一臺機器上的cookie至其他各個節點,cookie的預設路徑為/var/lib/rabbitmq/.erlang.cookie或者$HOME/.erlang.cookie,節點之間透過cookie確定相互是否可通訊。
b.配置各節點的hosts檔案( vim /etc/hosts)
xxx.xxx.xxx.xxx rmq-broker-test-1
xxx.xxx.xxx.xxx rmq-broker-test-2
xxx.xxx.xxx.xxx rmq-broker-test-3
......
xxx.xxx.xxx.xxx rmq-broker-test-10
c.逐個節點啟動RabbitMQ服務
rabbitmq-server -detached
d.檢視各個節點和叢集的工作執行狀態
rabbitmqctl status, rabbitmqctl cluster_status
e.以rmq-broker-test-1為主節點,在rmq-broker-test-2上:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster rabbit@rmq-broker-test-2
rabbitmqctl start_app
在其餘的節點上的操作步驟與rmq-broker-test-2虛擬機器上的一樣。
f.在RabbitMQ叢集中的節點只有兩種型別:記憶體節點/磁碟節點,單節點系統只執行磁碟型別的節點。而在叢集中,可以選擇配置部分節點為記憶體節點。
記憶體節點將所有的佇列,交換器,系結關係,使用者,許可權,和vhost的元資料資訊儲存在記憶體中。而磁碟節點將這些資訊儲存在磁碟中,但是記憶體節點的效能更高,為了保證叢集的高可用性,必須保證叢集中有兩個以上的磁碟節點,來保證當有一個磁碟節點崩潰了,叢集還能對外提供訪問服務。在上面的操作中,可以透過如下的方式,設定新加入的節點為記憶體節點還是磁碟節點:
#加入時候設定節點為記憶體節點(預設加入的為磁碟節點)
[root@mq-testvm1 ~]# rabbitmqctl join_cluster rabbit@rmq-broker-test-1 --ram
#也透過下麵方式修改的節點的型別
[root@mq-testvm1 ~]# rabbitmqctl changeclusternode_type disc | ram
g.最後可以透過“rabbitmqctl cluster_status”的方式來檢視叢集的狀態,上面搭建的10個節點的RabbitMQ叢集狀態(3個節點為磁碟節點,7個節點為記憶體節點)如下:
Cluster status of node 'rabbit@rmq-broker-test-1'
[{nodes,[{disc,['rabbit@rmq-broker-test-1','rabbit@rmq-broker-test-2',
'rabbit@rmq-broker-test-3']},
{ram,['rabbit@rmq-broker-test-9','rabbit@rmq-broker-test-8',
'rabbit@rmq-broker-test-7','rabbit@rmq-broker-test-6',
'rabbit@rmq-broker-test-5','rabbit@rmq-broker-test-4',
'rabbit@rmq-broker-test-10']}]},
{running_nodes,['rabbit@rmq-broker-test-10','rabbit@rmq-broker-test-5',
'rabbit@rmq-broker-test-9','rabbit@rmq-broker-test-2',
'rabbit@rmq-broker-test-8','rabbit@rmq-broker-test-7',
'rabbit@rmq-broker-test-6','rabbit@rmq-broker-test-3',
'rabbit@rmq-broker-test-4','rabbit@rmq-broker-test-1']},
{cluster_name,<<"rabbit@mq-testvm1">>},
{partitions,[]},
{alarms,[{'rabbit@rmq-broker-test-10',[]},
{'rabbit@rmq-broker-test-5',[]},
{'rabbit@rmq-broker-test-9',[]},
{'rabbit@rmq-broker-test-2',[]},
{'rabbit@rmq-broker-test-8',[]},
{'rabbit@rmq-broker-test-7',[]},
{'rabbit@rmq-broker-test-6',[]},
{'rabbit@rmq-broker-test-3',[]},
{'rabbit@rmq-broker-test-4',[]},
{'rabbit@rmq-broker-test-1',[]}]}]
(3)配置HAProxy
HAProxy提供高可用性、負載均衡以及基於TCP和HTTP應用的代理,支援虛擬主機,它是免費、快速並且可靠的一種解決方案。根據官方資料,其最高極限支援10G的併發。HAProxy支援從4層至7層的網路交換,即改寫所有的TCP協議。就是說,Haproxy 甚至還支援 Mysql 的均衡負載。為了實現RabbitMQ叢集的軟負載均衡,這裡可以選擇HAProxy。
關於HAProxy如何安裝的文章之前也有很多同學寫過,這裡就不再贅述了,有需要的同學可以參考下網上的做法。這裡主要說下安裝完HAProxy元件後的具體配置。
HAProxy使用單一配置檔案來定義所有屬性,包括從前端IP到後端伺服器。下麵展示了用於7個RabbitMQ節點組成叢集的負載均衡配置(另外3個磁碟節點用於儲存叢集的配置和元資料,不做負載)。同時,HAProxy執行在另外一臺機器上。HAProxy的具體配置如下:
#全域性配置
global
#日誌輸出配置,所有日誌都記錄在本機,透過local0輸出
log 127.0.0.1 local0 info
#最大連線數
maxconn 4096
#改變當前的工作目錄
chroot /apps/svr/haproxy
#以指定的UID執行haproxy行程
uid 99
#以指定的GID執行haproxy行程
gid 99
#以守護行程方式執行haproxy #debug #quiet
daemon
#debug
#當前行程pid檔案
pidfile /apps/svr/haproxy/haproxy.pid
#預設配置
defaults
#應用全域性的日誌配置
log global
#預設的樣式mode{tcp|http|health}
#tcp是4層,http是7層,health只傳回OK
mode tcp
#日誌類別tcplog
option tcplog
#不記錄健康檢查日誌資訊
option dontlognull
#3次失敗則認為服務不可用
retries 3
#每個行程可用的最大連線數
maxconn 2000
#連線超時
timeout connect 5s
#客戶端超時
timeout client 120s
#服務端超時
timeout server 120s
maxconn 2000
#連線超時
timeout connect 5s
#客戶端超時
timeout client 120s
#服務端超時
timeout server 120s
#系結配置
listen rabbitmq_cluster
bind 0.0.0.0:5672
#配置TCP樣式
mode tcp
#加權輪詢
balance roundrobin
#RabbitMQ叢集節點配置,其中ip1~ip7為RabbitMQ叢集節點ip地址
server rmq_node1 ip1:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node2 ip2:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node3 ip3:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node4 ip4:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node5 ip5:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node6 ip6:5672 check inter 5000 rise 2 fall 3 weight 1
server rmq_node7 ip7:5672 check inter 5000 rise 2 fall 3 weight 1
#haproxy監控頁面地址
listen monitor
bind 0.0.0.0:8100
mode http
option httplog
stats enable
stats uri /stats
stats refresh 5s
在上面的配置中“listen rabbitmqcluster bind 0.0.0.0:5671”這裡定義了客戶端連線IP地址和埠號。這裡配置的負載均衡演演算法是roundrobin—加權輪詢。與配置RabbitMQ叢集負載均衡最為相關的是“ server rmqnode1 ip1:5672 check inter 5000 rise 2 fall 3 weight 1”這種,它標識並且定義了後端RabbitMQ的服務。主要含義如下:
(a)“server
(b)“ip1:5672”部分:標識了後端RabbitMQ的服務地址;
(c)“check inter
可用;
(d)“rise
(e)“fall
#啟用HAProxy服務
[root@mq-testvm12 conf]# haproxy -f haproxy.cfg
啟動後,即可看到如下的HAproxy的介面圖:
(4)RabbitMQ的叢集架構設計圖
經過上面的RabbitMQ10個節點叢集搭建和HAProxy軟彈性負載均衡配置後即可組建一個中小規模的RabbitMQ叢集了,然而為了能夠在實際的生產環境使用還需要根據實際的業務需求對叢集中的各個實體進行一些效能引數指標的監控,從效能、吞吐量和訊息堆積能力等角度考慮,可以選擇Kafka來作為RabbitMQ叢集的監控佇列使用。因此,這裡先給出了一個中小規模RabbitMQ叢集架構設計圖:對於訊息的生產和消費者可以透過HAProxy的軟負載將請求分發至RabbitMQ叢集中的Node1~Node7節點,其中Node8~Node10的三個節點作為磁碟節點儲存叢集元資料和配置資訊。鑒於篇幅原因這裡就不在對監控部分進行詳細的描述的,會在後續篇幅中對如何使用RabbitMQ的HTTP API介面進行監控資料統計進行詳細闡述。
三、總結
本文主要詳細介紹了RabbitMQ叢集的工作原理和如何搭建一個具備負載均衡能力的中小規模RabbitMQ叢集的方法,並最後給出了RabbitMQ叢集的架構設計圖。限於筆者的才疏學淺,對本文內容可能還有理解不到位的地方,如有闡述不合理之處還望留言一起探討。
在此順便為自己打個Call,有興趣的朋友可以關註下我的個人公眾號:“匠心獨運的部落格”,對於Java併發、Spring和資料庫的一些細節、問題的文章將會在這個公眾號上釋出,歡迎交流與討論。
目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:
01. 除錯環境搭建
02. 專案結構一覽
03. API 配置(一)之應用
04. API 配置(二)之服務提供者
05. API 配置(三)之服務消費者
06. 屬性配置
07. XML 配置
08. 核心流程一覽
09. 拓展機制 SPI
10. 執行緒池
...
一共 60 篇++