基於高可用設定的RabbitMQ叢集實踐

2020-08-12 15:55:26

本文將提供一個基於高可用設定的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

基于高可用配置的RabbitMQ集群实践
▲RabbitMQ安裝過程

  另外,通過service rabbitmq-server status 命令可以檢視該服務是否正常執行

  5. 存取管理頁面

  重新啓動服務之後,在瀏覽器中輸入http://{server_ip}:15672,並用上面新建的admin/admin賬戶來登錄,即可監控RabbitMQ的執行情況。

  通過管理頁面,我們可以管理Connection、Channel、Exchange、Queue等元件,並能夠通過Admin模組來管理User、Virtual Host及Policy。

基于高可用配置的RabbitMQ集群实践
▲RabbitMQ管理頁面(需啓用管理外掛)

  6. 使用Java程式發送、接收訊息

  現在我們來通過java程式來實現一個簡單的生產者-消費者模型。生產者(P)發送訊息到Broker的Queue中,消費者(C)從Broker的Queue中獲取訊息。如下圖:

基于高可用配置的RabbitMQ集群实践
▲基於訊息佇列的生產者消費者模型

  6.1 編寫生產者java類:Send.java

基于高可用配置的RabbitMQ集群实践
▲Send.java

  6.2 編寫消費者java類:Recv.java

基于高可用配置的RabbitMQ集群实践
▲Recv.java

  6.3 執行程式Send.java及Recv.java,檢視訊息發送、接收情況。

基于高可用配置的RabbitMQ集群实践
▲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. 兩種叢集模式

基于高可用配置的RabbitMQ集群实践
▲RabbitMQ叢集結構

  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個節點:

基于高可用配置的RabbitMQ集群实践
▲各節點設定資訊(待設定)

  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

  可以看到,各節點均以單磁碟節點的叢集方式各自執行着。

基于高可用配置的RabbitMQ集群实践
▲各節點叢集資訊查詢(加入叢集前)

  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」

基于高可用配置的RabbitMQ集群实践
▲各節點叢集資訊查詢(加入集羣後)

基于高可用配置的RabbitMQ集群实践
▲從管理頁面看叢集資訊

  注:

  如果需要將某個節點從叢集中移除,使其變回獨立節點,可以使用以下命令:

  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 '.*' '.*' '.*'

基于高可用配置的RabbitMQ集群实践
▲重新新增管理員賬戶

  5. 搭建高可用叢集環境

  通過Policy(策略)設定映象佇列,來實現RabbitMQ的高可用方案。策略主要用來控制和修改叢集範圍的某個Virtual Host中Exchange和Queue的行爲。具體有以下三種策略型別:

基于高可用配置的RabbitMQ集群实践
▲策略的3種類型

  這裏,我們採用以下策略(在叢集中任意節點啓用策略,策略會自動同步到各個叢集節點):

  #同步以"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中(修改部分如下圖):

基于高可用配置的RabbitMQ集群实践
▲從Node1發送訊息到映象佇列

  6.2 修改消費者程式(Recv.java),連線到Node3(192.168.1.30)節點,接收ha.hello佇列中的訊息(修改部分如下圖):

基于高可用配置的RabbitMQ集群实践
▲從Node3接收映象佇列的訊息

  6.3 首先執行生產者程式(Send.java)發送100條訊息,然後通過命令「rabbitmqctl stop」停止Node1的RabbitMQ服務,再執行消費者程式(Recv.java)接收訊息。

基于高可用配置的RabbitMQ集群实践
▲發送訊息到ha.hello佇列

基于高可用配置的RabbitMQ集群实践
▲停止Node1節點

基于高可用配置的RabbitMQ集群实践
▲ha.hello佇列中訊息被成功消費

  7. 基於叢集環境的用戶端開發優化建議

  上面介紹的Send.java與Recv.java程式碼,存在以下兩點不足需要改進:

  1、生產者、消費者都只連線到了叢集中的某個節點。如果該節點故障之後,用戶端程式將無法正常發送或接收訊息;

  2、沒有設定自動重連機制 機製,使得用戶端程式與Broker之間建立的TCP連線很脆弱。一旦由於網路異常導致Connection關閉,用戶端程式將程式將無法正常接收訊息。

  基於上述兩個實際用戶端開發的痛點,我們需要對程式進行叢集全節點連線、自動重連的改進。改進後的Recv.java完整程式碼如下圖19~圖21所示:

  首先讓Recv類實現Runnable、Consumer介面,讓Recv範例以Consomer執行緒的方式執行。

基于高可用配置的RabbitMQ集群实践
▲實現Runnable及Consumer介面

  然後在重寫run方法中進行自動重連、連線到所有節點的設定

基于高可用配置的RabbitMQ集群实践
▲實現run方法,在其中設定

  然後重寫handleDelivery方法,來設定接收到訊息後的處理邏輯;並空實現Consumer介面中其他handleXXX的方法;最後通過main方法以執行緒的方式建立Consumer的範例來接收訊息。

基于高可用配置的RabbitMQ集群实践
▲重寫handleDelivery方法實現訊息處理

    四、總結

  本文通過介紹RabbitMQ的基本概念、主要作用和使用場景,回答了什麼是RabbitMQ,以及用RabbitMQ能做什麼的問題;然後通過搭建一個RabbitMQ單節點環境、用程式演示訊息發送接收過程,回答了怎麼使用RabbitMQ的問題;再通過搭建高可用叢集環境,回答瞭如何實現RabbitMQ服務高可用性的問題。並分享了自己在叢集環境的用戶端開發實踐中的一些經驗。

  對於上述的RabbitMQ高可用叢集方案,還存在一定的缺點:雖然在用戶端程式提供完整的叢集節點資訊能保證連線的可靠性,但是這種向用戶端程式暴露叢集(所有)節點的做法不太合適。

  當叢集環境發生變化(比如增加、刪除節點)時,用戶端程式還得做相應的設定變更,增加了叢集環境和用戶端程式耦合性。RabbitMQ官方建議通過在RabbitMQ叢集環境之上增加一個抽象層,讓用戶端程式連線到該抽象層,實現叢集環境和用戶端程式的解耦。這個抽象層,可以是一個包含一段TTL設定的動態DNS服務,也可以是一個TCP LB(基於TCP協定的Load Balancer,如HAProxy)。