本文將提供一個基於高可用設定的RabbitMQ叢集方案。通過介紹RabbitMQ的基本概念、主要作用和使用場景,並搭建RabbitMQ單節點環境、用程式演示訊息發送接收過程,以及搭建RabbitMQ高可用叢集環境,來生動而完整地介紹整個叢集方案。並結合自己的實踐,分享一些在叢集環境下的用戶端開發優化經驗。
本文內容主要包括以下幾個方面:
1、RabbitMQ基礎介紹
2、RabbitMQ單節點環境的實踐
3、RabbitMQ高可用叢集環境的實踐
4、總結
一、 RabbitMQ基礎介紹
RabbitMQ是基於AMQP(Advanced Message Queuing Protocol,高階訊息佇列協定)實現的訊息中介軟體。訊息中介軟體主要用於元件之間的解耦,訊息的發送者無需知道訊息接收者的存在,反之亦然。RabbitMQ有很多優點:
1、支援叢集環境和高可用佇列,魯棒性強
2、支援豐富的開發平臺,如Java、.NET、Python、Perl、PHP、JavaScript等;
3、可執行在大多數操作系統;
4、開源、有活躍的社羣、有豐富的外掛。
RabbitMQ作爲訊息代理(message broker),給我們提供了發送、接收訊息的平臺,並能在訊息被接收之前保持其永續性。通常用於應用程式之間、以及程式內各元件之間的訊息傳遞等場景。比如,用於實現Java程式與Perl程式之間的訊息傳遞;又如在Java Web程式中用於後臺功能模組與前臺通知模組之間的訊息傳遞,等等。
在使用RabbitMQ進行用戶端開發之前,我們需要瞭解下它的基本元件和概念:
Broker: 訊息佇列伺服器,是接受用戶端連線,實現AMQP訊息佇列和路由功能的進程。
1、Virtual Host: 是一個邏輯概念,一個Virtual Host裏面可以有若幹個Exchange和Queue,它是許可權控制的最小單元。
2、Queue: 訊息佇列載體,用於儲存訊息。
3、Exchange: 路由,用於接收訊息並根據Binding規則將訊息路由給伺服器中的佇列。
4、Binding: 系結,用於把Exchange和Queue按照路由規則系結起來。
5、RoutingKey: 路由關鍵字,Exchange根據這個關鍵字進行訊息投遞。
6、Connection: 連線,一個位於用戶端和Broker之間的TCP連線。
7、Channel: 訊息通道,在用戶端的每個Connection裡可建立多個Channel,每個channel代表一個對談。之所以需要Channel,是因爲TCP連線的建立和釋放都是十分昂貴的。
二、 RabbitMQ單節點環境的實踐
上面簡單介紹了RabbitMQ的作用和優點,以及基本元件和概念。接下來,我們以Ubuntu 14.04 LTS 64bit的伺服器爲例,動手搭建一個RabbitMQ Server的單節點環境(當前最新版是rabbitmq-server_3.6.5),並編寫java程式來測試RabbitMQ發送、接受訊息的具體過程,具體步驟如下:
1. 通過APT方式安裝RabbitMQ服務
1.1 新增APT庫到本地軟體更新的伺服器地址列表中:
echo 'deb http://www.rabbitmq.com/debian/ testing main' |sudo tee /etc/apt/sources.list.d/rabbitmq.list
1.2 下載RabbitMQ的公鑰並新增到本地trusted數據庫中:
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc |sudo apt-key add -
1.3 更新APT庫軟體包列表:
sudo apt-get update
1.4 安裝RabbitMQ軟體包(安裝之後預設啓動):
sudo apt-get install rabbitmq-server
2. 啓用常用外掛,包括管理外掛、web-stomp外掛等
rabbitmq-plugins enable rabbitmq_management rabbitmq_web_stomp rabbitmq_stomp
3. 新增管理使用者並授權
RabbitMQ安裝之後,會建立預設的Virtual Host(即「/」),以及預設的管理賬戶(guest/guest),但該guest賬戶只能以localhost方式連線到Broker。如果需要遠端存取Broker, 我們可以通過設定新的管理員賬戶來實現。
rabbitmqctl add_user admin admin
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin '.*' '.*' '.*'
4. 重新啓動服務
service rabbitmq-server restart
另外,通過service rabbitmq-server status 命令可以檢視該服務是否正常執行
5. 存取管理頁面
重新啓動服務之後,在瀏覽器中輸入http://{server_ip}:15672,並用上面新建的admin/admin賬戶來登錄,即可監控RabbitMQ的執行情況。
通過管理頁面,我們可以管理Connection、Channel、Exchange、Queue等元件,並能夠通過Admin模組來管理User、Virtual Host及Policy。
6. 使用Java程式發送、接收訊息
現在我們來通過java程式來實現一個簡單的生產者-消費者模型。生產者(P)發送訊息到Broker的Queue中,消費者(C)從Broker的Queue中獲取訊息。如下圖:
6.1 編寫生產者java類:Send.java
6.2 編寫消費者java類:Recv.java
6.3 執行程式Send.java及Recv.java,檢視訊息發送、接收情況。
三、 RabbitMQ高可用叢集環境的實踐
叢集是保證服務可靠性的一種方式,同時可以通過水平擴充套件以提升訊息吞吐能力。RabbitMQ是用分佈式程式設計語言erlang開發的,所以天生就支援叢集。接下來,將介紹RabbitMQ分佈式訊息處理方式、叢集模式、節點型別,並動手搭建一個高可用叢集環境,最後通過java程式來驗證叢集的高可用性。
1. 三種分佈式訊息處理方式
RabbitMQ分佈式的訊息處理方式有以下三種:
1、Clustering:不支援跨網段,各節點需執行同版本的Erlang和RabbitMQ, 應用於同網段區域網。
2、Federation:允許單台伺服器上的Exchange或Queue接收發布到另一臺伺服器上Exchange或Queue的訊息, 應用於廣域網,。
3、Shovel:與Federation類似,但工作在更低層次。
RabbitMQ對網路延遲很敏感,在LAN環境建議使用clustering方式;在WAN環境中,則使用Federation或Shovel。我們平時說的RabbitMQ叢集,說的就是clustering方式,它是RabbitMQ內嵌的一種訊息處理方式,而Federation或Shovel則是以plugin形式存在。
2. 兩種叢集模式
2.1 普通模式(預設)
圖7是由3個節點(Node1,Node2,Node3)組成的RabbitMQ普通叢集環境,Exchange A的元數據資訊在所有節點上是一致的;而Queue的完整資訊只有在建立它的節點上,各個節點僅有相同的元數據,即佇列結構。
當producer發送訊息到Node1節點的Queue1中後,consumer從Node3節點拉取時,RabbitMQ會臨時在Node1、Node3間進行訊息傳輸,把Node1中的訊息實體取出並經過Node3發送給consumer。
該模式存在一個問題:當Node1節點發生故障後,Node3節點無法取到Node1節點中還未被消費的訊息實體。如果訊息沒做持久化,那麼訊息將永久性丟失;如果做了持久化,那麼只有等Node1節點故障恢復後,訊息才能 纔能被其他節點消費。
2.2 映象模式(基於映象佇列)
它是在普通模式的基礎上,把需要的佇列做成映象佇列,存在於多個節點來實現高可用(HA)。該模式解決了上述問題,Broker會主動地將訊息實體在各映象節點間同步,在consumer取數據時無需臨時拉取。
該模式帶來的副作用也很明顯,除了降低系統效能外,如果映象佇列數量過多,加之大量的訊息進入,叢集內部的網路頻寬將會被大量消耗。通常地,對可靠性要求較高的場景建議採用映象模式。
3. 兩種叢集節點型別
1、RAM Node(記憶體節點):將所有的Virtual Host、Queue、Exchange、Binding、User等的元數據儲存在記憶體中,因此效能比較出色。
2、DISC Node(磁碟節點): 將元數據儲存在磁碟中。
注:RabbitMQ單節點環境只允許是磁碟節點,防止重新啓動RabbitMQ時丟失系統的設定資訊。RabbitMQ叢集環境至少要有一個磁碟節點,因爲當節點加入或者離開叢集時,必須要將該變更通知到至少一個磁碟節點。
4. 搭建普通叢集環境
現在,將以圖7爲例來搭建普通叢集環境。
4.1 首先根據上文「RabbitMQ單節點環境的搭建」章節相關內容,準備好以下3個節點:
4.2 設定各節點的hostname:
vim /etc/hostname
……
reboot
注:其他Linux發行版可能需要通過「vim /etc/sysconfig/network」來修改hostname
4.3 修改各節點的hosts檔案,並新增以下DNS資訊:
vim /etc/hosts
192.168.1.10 rabbit01 rabbit01
192.168.1.20 rabbit02 rabbit02
192.168.1.30 rabbit03 rabbit03
4.4 將各節點的erlang.cookie設定爲相同值,比如都使用rabbit01節點的值:
#先備份原cookie檔案
rabbit02# cp /var/lib/rabbitmq/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie.bak
rabbit03# cp /var/lib/rabbitmq/.erlang.cookie /var/lib/rabbitmq/.erlang.cookie.bak
#修改cookie的值
chmod 777 /var/lib/rabbitmq/.erlang.cookie
vim /var/lib/rabbitmq/.erlang.cookie
……
chmod 400 /var/lib/rabbitmq/.erlang.cookie
4.5 停止所有節點上的RabbitMQ服務,然後以detached方式獨立執行:
rabbit01# rabbitmqctl stop
rabbit02# rabbitmqctl stop
rabbit03# rabbitmqctl stop
rabbit01# rabbitmq-server -detached
rabbit02# rabbitmq-server -detached
rabbit03# rabbitmq-server -detached
4.6 檢視各節點的叢集資訊:
rabbit01# rabbitmqctl cluster_status
rabbit02# rabbitmqctl cluster_status
rabbit03# rabbitmqctl cluster_status
可以看到,各節點均以單磁碟節點的叢集方式各自執行着。
4.7 將rabbit02、rabbit03 作爲記憶體節點,加入到rabbit01的叢集中
rabbit02# rabbitmqctl stop_app
rabbit02# rabbitmqctl join_cluster --ram rabbit@rabbit01
rabbit02# rabbitmqctl start_app
rabbit03# rabbitmqctl stop_app
rabbit03# rabbitmqctl join_cluster --ram rabbit@rabbit01
rabbit03# rabbitmqctl start_app
4.8 再次檢視各節點的叢集資訊:
rabbit01# rabbitmqctl cluster_status
rabbit02# rabbitmqctl cluster_status
rabbit03# rabbitmqctl cluster_status
可以看到,各節點處於一個由rabbit01(disc node)、rabbit02(ram node)、rabbit03(ram node)組成的叢集,名爲「rabbit@rabbit01」
注:
如果需要將某個節點從叢集中移除,使其變回獨立節點,可以使用以下命令:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
如果需要啓停某個節點來進行維護,可以使用以下命令:
rabbitmqctl stop
#FormatImgID_24##FormatImgID_25#rabbitmq-server -detached
當新的節點加入到叢集之後,其使用者資訊也被重置了(之前新增的admin賬戶不見了),需要重新設定管理員使用者,以便存取RabbitMQ管理頁面(在任意節點新增使用者,會自動同步到各個叢集節點):
#新增管理員使用者並授權:
rabbit01# rabbitmqctl add_user admin admin
rabbit01# rabbitmqctl set_user_tags admin administrator
rabbit01# rabbitmqctl set_permissions -p / admin '.*' '.*' '.*'
5. 搭建高可用叢集環境
通過Policy(策略)設定映象佇列,來實現RabbitMQ的高可用方案。策略主要用來控制和修改叢集範圍的某個Virtual Host中Exchange和Queue的行爲。具體有以下三種策略型別:
這裏,我們採用以下策略(在叢集中任意節點啓用策略,策略會自動同步到各個叢集節點):
#同步以"ha."開頭的佇列到叢集中各節點,應用於所有節點(包括新增節點)
#FormatImgID_27##FormatImgID_28#rabbitmqctl set_policy my-ha-all "^ha\." '{"ha-mode":"all",,"ha-sync-mode":"automatic"}'
rabbitmqctl set_policy my-ha-all "^ha\." '{"ha-mode":"all"}'
6. 驗證叢集的高可用性
接下來,基於圖7的叢集環境來驗證叢集中映象佇列的高可用性。
6.1 修改生產者程式(Send.java),連線到Node1(192.168.1.10)節點,發送訊息到映象佇列ha.hello中(修改部分如下圖):
6.2 修改消費者程式(Recv.java),連線到Node3(192.168.1.30)節點,接收ha.hello佇列中的訊息(修改部分如下圖):
6.3 首先執行生產者程式(Send.java)發送100條訊息,然後通過命令「rabbitmqctl stop」停止Node1的RabbitMQ服務,再執行消費者程式(Recv.java)接收訊息。
7. 基於叢集環境的用戶端開發優化建議
上面介紹的Send.java與Recv.java程式碼,存在以下兩點不足需要改進:
1、生產者、消費者都只連線到了叢集中的某個節點。如果該節點故障之後,用戶端程式將無法正常發送或接收訊息;
2、沒有設定自動重連機制 機製,使得用戶端程式與Broker之間建立的TCP連線很脆弱。一旦由於網路異常導致Connection關閉,用戶端程式將程式將無法正常接收訊息。
基於上述兩個實際用戶端開發的痛點,我們需要對程式進行叢集全節點連線、自動重連的改進。改進後的Recv.java完整程式碼如下圖19~圖21所示:
首先讓Recv類實現Runnable、Consumer介面,讓Recv範例以Consomer執行緒的方式執行。
然後在重寫run方法中進行自動重連、連線到所有節點的設定
然後重寫handleDelivery方法,來設定接收到訊息後的處理邏輯;並空實現Consumer介面中其他handleXXX的方法;最後通過main方法以執行緒的方式建立Consumer的範例來接收訊息。
四、總結
本文通過介紹RabbitMQ的基本概念、主要作用和使用場景,回答了什麼是RabbitMQ,以及用RabbitMQ能做什麼的問題;然後通過搭建一個RabbitMQ單節點環境、用程式演示訊息發送接收過程,回答了怎麼使用RabbitMQ的問題;再通過搭建高可用叢集環境,回答瞭如何實現RabbitMQ服務高可用性的問題。並分享了自己在叢集環境的用戶端開發實踐中的一些經驗。
對於上述的RabbitMQ高可用叢集方案,還存在一定的缺點:雖然在用戶端程式提供完整的叢集節點資訊能保證連線的可靠性,但是這種向用戶端程式暴露叢集(所有)節點的做法不太合適。
當叢集環境發生變化(比如增加、刪除節點)時,用戶端程式還得做相應的設定變更,增加了叢集環境和用戶端程式耦合性。RabbitMQ官方建議通過在RabbitMQ叢集環境之上增加一個抽象層,讓用戶端程式連線到該抽象層,實現叢集環境和用戶端程式的解耦。這個抽象層,可以是一個包含一段TTL設定的動態DNS服務,也可以是一個TCP LB(基於TCP協定的Load Balancer,如HAProxy)。