1. 程式人生 > >理解 OpenStack 高可用(HA)(5):RabbitMQ HA

理解 OpenStack 高可用(HA)(5):RabbitMQ HA

本系列會分析OpenStack 的高可用性(HA)概念和解決方案:

1. RabbitMQ 叢集

    你可以使用若干個RabbitMQ 節點組成一個 RabbitMQ 叢集。叢集解決的是擴充套件性問題。所有的資料和狀態都會在叢集內所有的節點上被複制,只有queue是例外。預設的情況下,訊息只會存在於它被建立的節點上,但是它們在所有節點上可見和可訪問。

    對於Queue 來說,訊息實體只存在於其中一個節點,A、B兩個節點僅有相同的元資料,即佇列結構。當訊息進入A節點的Queue中後,consumer從B節點拉取時,RabbitMQ會臨時在A、B間進行訊息傳輸,把A中的訊息實體取出並經過B傳送給consumer。所以consumer應儘量連線每一個節點,從中取訊息。即對於同一個邏輯佇列,要在多個節點建立物理Queue。否則無論consumer連A或B,出口總在A,會產生瓶頸。該模式存在一個問題就是當A節點故障後,B節點無法取到A節點中還未消費的訊息實體。如果做了訊息持久化,那麼得等A節點恢復,然後才可被消費;如果沒有持久化的話,然後就沒有然後了。

   因此,在叢集環境中,佇列只有元資料會在叢集的所有節點同步,但佇列中的資料只會存在於一個節點,資料沒有冗餘且容易丟,甚至在durable的情況下,如果所在的伺服器節點宕機,就要等待節點恢復才能繼續提供訊息服務。那既然有這種問題,為什麼依然有這個選項呢?官方的說法是:

   (1)儲存空間:如果叢集的每個節點都有每個queue的一個拷貝,那麼增加節點將無法增加儲存容量。比如,如果一個節點可以存放 1GB 的訊息,增加另外兩個節點只會增加另外兩個拷貝而已。

  (2)效能:釋出訊息,將會將它拷貝其它節點上。如果是永續性訊息,那麼每個節點上都會觸發磁碟操作。你的網路和磁碟負載在每次增加節點時都會增加。

  可見,RabbitMQ Clustering 技術不能完全解決HA 問題。單純的叢集只適合於在不需要HA的場景中使用。

2. Active/Passive HA 方案

    RabbitMQ A/P HA 官方方案 是採用 Pacemaker + (DRBD 或者其它可靠的共享 NAS/SNA 儲存) + (CoroSync 或者 Heartbeat 或者 OpenAIS)組合來實現的。需要注意的是 CoroSync 需要使用多播,而多播在某些雲環境中是被禁用的,這時候可以改為使用 Heartbeat,它採用單播。其架構為:

實現 HA 的原理:

  1. RabbitMQ 將資料庫檔案和
    永續性訊息儲存在 DRBD 掛載點上。注意,在 failover 時,非永續性訊息會丟失。
  2. DRBD 要求在某一時刻,只能有一個 node (primary)能夠訪問其共享儲存,因此,不會有多個node 同時寫一塊資料的風險。這要求必須依賴 Pacemaker 來控制使用 DRBD 共享儲存的 node。Pacemaker 使得在某一時刻,只有 active node 來訪問 DRBD 共享儲存。在 failover 時,Pacemaker 會解除安裝當前 active node 上的 DRBD 共享儲存,並在新的 active node (原 secondary node)上掛載 DRBD 共享儲存。
  3. 在 node 啟動時,不會自動啟動 RabbitMQ。Pacemaker 會在 active node 啟動 RabbitMQ。
  4. RabbitMQ HA 只會提供一個訪問 RabbitMQ 的 虛擬IP 地址。該方案依賴 Pacemaker 來保證 VIP 的切換。

2.1 基於 Pacemaker + DRBD + CoroSync 的 RabbitMQ HA 方案配置

    RabbitMQ 官方的 這篇文章介紹了基於 Pacemaker 的 RabbitMQ HA 方案。它同時指出,這是傳統的 RabbitMQ HA 方案,並且建議使用 RabbitMQ 叢集 + 映象 Message Queue 的方式來實現 A/A HA。使用 Pacemaker 實現 HA 的方案主要步驟包括:

  1. 為 RabbitMQ 配置一個 DRBD 裝置

  2. 配置 RabbitMQ 使用建立在 DRBD 裝置之上的資料目錄

  3. 選擇並繫結一個可以在各叢集節點之間遷移的虛擬 IP 地址 (即 VIP )

  4. 配置 RabbitMQ 監聽該 IP 地址

  5. 使用 Pacemaker 管理上述所有資源,包括 RabbitMQ 守護程序本身


2. 基於叢集+映象佇列的A/A 方案 

    從 3.6.0 版本開始,RabbitMQ 支援映象佇列功能,官方文件在這裡。。與普通叢集相比,其實質和普通模式不同之處在於,訊息實體會主動在映象節點間同步,而不是在 consumer 取資料時臨時拉取。該模式帶來的副作用也很明顯,除了降低系統性能外,如果映象佇列數量過多,加之大量的訊息進入,叢集內部的網路頻寬將會被這種同步通訊大大消耗掉。所以在對可靠性要求較高的場合中適用。 

    Mirrorred queue 是 RabbitMQ 高可用的一種方案,相對於普通的叢集方案來講,queue中的訊息每個節點都會存在一份 copy, 這個在單個節點失效的情況下,整個叢集仍舊可以提供服務。但是由於資料需要在多個節點複製,在增加可用性的同時,系統的吞吐量會有所下降。 

    選舉機制:mirror queue 內部實現了一套選舉演算法,有一個 master 和多個slave,queue 中的訊息以 master 為主。映象佇列有主從之分,一個主節點(master),0個或多個從節點(slave)。當master宕掉後,會在 slave 中選舉新的master,其選舉演算法為最早啟動的節點。 若master節點失效,則 mirror queue 會自動選舉出一個節點(slave中訊息佇列最長者)作為master,作為訊息消費的基準參考; 在這種情況下可能存在ack訊息未同步到所有節點的情況(預設非同步),若 slave 節點失效,mirror queue 叢集中其他節點的狀態無需改變。所以,看起來可以使用兩節點的映象佇列叢集。 

    使用:對於publish,可以選擇任意一個節點進行連線,rabbitmq內部若該節點不是master,則轉發給master,master向其他slave節點發送該訊息,後進行訊息本地化處理,並組播複製訊息到其他節點儲存;對於consumer,可以選擇任意一個節點進行連線,消費的請求會轉發給master,為保證訊息的可靠性,consumer需要進行ack確認,master收到ack後,才會刪除訊息,ack訊息會同步(預設非同步)到其他各個節點,進行slave節點刪除訊息。   

   考慮到效能問題,複製可以在可控範圍內進行,包括叢集內全節點複製的映象佇列和叢集內區域性節點複製的映象佇列兩種模式。 

2.1 配置

    多個單獨的 RabbitMQ 服務,可以加入到一個叢集中,也可以從叢集中退出。叢集中的 RabbitMQ 服務,使用同樣的 Erlang cookie(unix 系統上預設為 /var/lib/rabbitmq/.erlang.cookie)。所有

  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
在一個 RabbitMQ 服務中被操作的資料和狀態(date/state)都會被同步到叢集中的其它節點。  

   映象佇列的配置通過新增 policy 完成,policy 新增的命令為:

  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

2.2 HAProxy 和 RabbitMQ A/A 叢集

RabbitMQ 實現映象佇列的方式比較特別。這篇文章進行了深入的闡述。假設有如下的配置:

建立 queue 的過程:

  1. LB 將 client request 分發到 node 2,client 建立佇列 “NewQueue”,然後開始向其中放入 message。
  2. 最終,後端服務會對 node 2 上的 “NewQueue” 建立一個快照,並在一段時間內將其拷貝到node 1 和 3 上。這時候,node2 上的佇列是 master Queue,node 1 和 3 上的佇列是 slave queue。

假如現在 node2 宕機了:

  • node 2 不再響應心跳,它會被認為已經被從叢集中移出了
  • node 2 上的 master queue 不再可用
  • RabbitMQ 將 node 1 或者 3 上的 salve instance 升級為 master instance

假設 master queue 還在 node 2 上,客戶端通過 LB 訪問該佇列:


  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

  1. 客戶端連線到叢集,要訪問 “NewQueue” 佇列
  2. LB 根據配置的輪詢演算法將請求分發到一個節點上
  3. 假設客戶端請求被轉到 node 3 上
  4. RabbitMQ 發現 “NewQueue” master node 是 node 2
  5. RabbitMQ 將訊息轉到 node 2 上
  6. 最終客戶端成功連線到 node 2 上的 master 佇列

可見,這種配置下,2/3 的客戶端請求需要重定向,這會造成大概率的訪問延遲,但是終究訪問還是會成功的。要優化的話,總共有兩種方式:

  • 直接連到 master queue 所在的節點,這樣就不需要重定向了。但是對這種方式,需要提前計算,然後告訴客戶端哪個節點上有 master queue。
  • 儘可能地在所有節點間平均分佈佇列,減少重定向概率

  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

2.3 映象佇列的負載均衡

     使用映象佇列的 RabbitMQ 不支援負載均衡,這是由其映象佇列的實現機制決定的。如前面所述,假設一個叢集裡有兩個例項,記作 rabbitA 和 rabbitB。如果某個佇列在rabbitA 上建立,隨後在 rabbitB 上映象備份,那麼 rabbitA 上的佇列稱為該佇列的主佇列(master queue),其它備份均為從佇列。接下來,無論client 訪問rabbitA 或 rabbitB,最終消費的佇列都是主佇列。換句話說,即使在連線時主動連線rabbitB,RabbitMQ的 cluster 會自動把連線轉向 rabbitA。當且僅當rabbitA服務down掉以後,在剩餘的從佇列中再選舉一個作為繼任的主佇列。

    如果這種機制是真的(需要看程式碼最最終確認),那麼負載均衡就不能簡單地隨機化連線就能做到了。要實現輪詢,需要滿足下面的條件:

  • 佇列本身的建立需要隨機化,即將佇列分佈於各個伺服器
  • client 訪問需要知道每個佇列的主佇列儲存在哪個伺服器
  • 如果某個伺服器down了,需要知道哪個從佇列被選擇成為繼任的主佇列。

   要實現這種方案,這篇文章 給出了一個方案:

首先,在建立一個新佇列的時候,Randomiser 會隨機選擇一個伺服器,這樣能夠保證佇列均勻分散在各個伺服器(這裡暫且不考慮負載)。建立佇列後需要在Meta data 裡記錄這個佇列對應的伺服器;另外,Monitor Service是關鍵,它用於處理某個伺服器down掉的情況。一旦發生down機,它需要為之前主佇列在該伺服器的佇列重新建立起與伺服器的對映關係。


  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

這裡會遇到一個問題,即怎麼判斷某個佇列的主佇列呢?一個方法是通過rabbitmqctl,如下面的例子: 

1 2 3 ./rabbitmqctl -p production list_queues pid slave_pids registration-email-queue        <rabbit@mq01.2.1076.0>       [<rabbit@mq00.1.285.0>] registration-sms-queue  <rabbit@mq01.2.1067.0>       [<rabbit@mq00.1.281.0>]

 可以看到pid和slave_pids分別對應主佇列所在的伺服器和從伺服器(可能有多個)。利用這個命令就可以瞭解每個佇列所在的主伺服器了。


  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

3. OpenStack 中RabbitMQ叢集的用法和配置

3.1 配置

    OpenStack 官方建議至少使用三節點 RabbitMQ 叢集,而且推薦配置是使用映象佇列。對於測試和演示環境,使用兩節點也是可以。以下OpenStack 服務都支援這種 A/A 形式的 RabbitMQ:

  • 計算服務
  • 塊裝置儲存服務
  • 網路服務
  • Telemetry

  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

   OpenStack 支援如下的 RabbitMQ 配置:

  • rabbit_hosts=rabbit1:5672,rabbit2:5672,rabbit3:5672:所有RabbitMQ 服務列表
  • rabbit_retry_interval=1: 連線失敗時候的重試間隔
  • rabbit_retry_backoff=2: How long to back-off for between retries when connecting to RabbitMQ。不太明白其含義。
  • rabbit_max_retries=0:最大重試次數。0 表示一直重試
  • rabbit_durable_queues=true:true 的話表示使用永續性佇列,Kilo 中預設為 false。
  • rabbit_ha_queues=true: 設定為 true 的話則使用映象佇列,並設定 x-ha-policy 為 all;但是 Kilo 中其預設值為 false。

具體的配置可以參考 這篇文章 以及 OpenStack 官網,以及 RabbitMQ 官網


  • rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
  • -p Vhost: 可選引數,針對指定vhost下的queue進行設定
  • Name: policy的名稱
  • Pattern: queue的匹配模式(正則表示式)
  • Definition: 映象定義,包括三個部分 ha-mode,ha-params,ha-sync-mode
    • ha-mode: 指明映象佇列的模式,有效值為 all/exactly/nodes
      • all表示在叢集所有的節點上進行映象
      • exactly 表示在指定個數的節點上進行映象,節點的個數由ha-params指定
      • nodes 表示在指定的節點上進行映象,節點名稱通過ha-params指定
    • ha-params: ha-mode模式需要用到的引數
    • ha-sync-mode: 映象佇列中訊息的同步方式,有效值為automatic,manually
  • Priority: 可選引數, policy的優先順序
複製程式碼

例如,對佇列名稱以 ’hello‘ 開頭的所有佇列進行映象,並在叢集的兩個節點上完成映象,policy的設定命令為:

rabbitmqctl  set_policy  hello-ha  "^hello"  '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

3.3 效能配置

OpenStack 支援如下幾種 RPC 效能配置:

  • rpc_conn_pool_size = 30 (IntOpt) (RPC 連線池的大小)
  • rpc_response_timeout = 60 (IntOpt) (等待返回的超時時間,單位是秒)
  • rpc_thread_pool_size = 64 (IntOpt) (RPC 執行緒池的大小)

 這些引數在做RPC 效能除錯的時候往往需要考慮到。

參考連結: