天天看點

Highly Available (Mirrored) Queues高可用(鏡像)隊列

預設情況下,queues存放在RabbitMQ叢集的單個節點之上。exchanges和bindings恰恰相反,在叢集中的所有節點中都有存檔。queues可以配置鏡像以此可以在多個節點中有備份。每個鏡像隊列包含一個master節點和一個或者多個slave節點。如果master節點由于某種原因失效,那麼“資曆最老”的slave節點将被提升為新的master節點。(譯者注:根據進入的時間排序,時間最長的節點即為資曆最老的節點。)

發送的消息将被複制到隊列的所有鏡像節點。Consumer連接配接的是master節點,而不是其他的slave節點(譯者注:就算程式連接配接的是slave節點,也會路由到master節點,然後建立tcp連接配接),被消費并被确認的消息将會被清除。鏡像隊列增強了其可用性,但是并沒有分攤負載。

這裡并不推薦在區域網路之外搭建RabbitMQ叢集,這樣會有網絡分區的風險。同時,用戶端程式最好也部署在相同的區域網路之内。

在我們示範怎麼配置鏡像隊列之前先看下之前是怎麼使用的,然後我們再來陳述現在推薦怎麼使用。

Policies可以在任何适合改變,比如期初你建立了一個無鏡像的queue,在之後的某個節點你可以為這個queue配置鏡像,反之亦然。對于沒有配置鏡像的queue(non-mirrored)和配置了鏡像但是又沒有slave鏡像節點的queue之間是有差別的,前者卻反了額外的鏡像機制,但是可以有更高的吞吐量。

通過policy建立對象,這裡包括了兩個關鍵的參數:ha-mode和ha-params(可選)。下表做了相應的解釋

ha-mode

ha-params

Result

all

(absent)

在叢集中的每個節點都有鏡像。當一個節點添加到叢集中時,這個節點同樣會有相應的鏡像

exactly

count

指定在叢集中鏡像的個數。如果叢集中節點的個數小于count的值,那麼所有的節點都會配置鏡像。如果其中一個鏡像挂掉,那麼會在另一個節點生成新的鏡像。ha-mode:exactly和ha-promote-on-shutdown:always一起使用将會很危險。

nodes

node names

在指定的節點清單中配置鏡像。節點名稱可以通過rabbitmqctl cluster_status指令擷取,通常名稱是“rabbit@hostname”的這種形式。如果這些指定的節點都處于不可用狀态(當機或者關閉服務等),那麼用戶端程式會在自己所連接配接的那麼節點上建立queue。

(譯者注:當所有slave都出在(與master)未同步狀态時,并且ha-promote-on-shutdown設定為when-synced(預設)時,如果master因為主動的原因停掉,比如是通過rabbitmqctl stop指令停止或者優雅關閉OS,那麼slave不會接管master,也就是此時鏡像隊列不可用;但是如果master因為被動原因停掉,比如VM或者OS crash了,那麼slave會接管master。這個配置項隐含的價值取向是保證消息可靠不丢失,放棄可用性。如果ha-promote-on-shutdown設定為always,那麼不論master因為何種原因停止,slave都會接管master,優先保證可用性。

試想一下,如果ha-mode設定為exactly,ha-params設定為2,當其中一個鏡像節點挂掉,那麼在叢集中的另一個節點将會被設定為鏡像,此鏡像尚未與master同步,此時master節點也挂掉,那麼這個鏡像将被提升為master,造成資料丢失。)

ha-mode:all是一種非常保守且不必要選擇。在有三個或者更多節點的叢集中推薦配置大多數個數即可. 比如3個幾點的叢集配置為2, 或者5個節點的叢集配置為3。有些資料是瞬時性的,對延遲要求比較高,可以配置更少的鏡像個數,甚至可以不配置鏡像。

每個queue都有一個master節點,所有對于queue的操作都是事先在master上完成,之後再slave上進行相同的操作。保證消息的FIFO順序是非常必要的。

每個不同的queue可以坐落在不同的叢集節點上,這些queue如果配置了鏡像隊列,那麼會有1個master和多個slave。基本上所有的操作都落在master上,那麼如果這些queues的master都落在個别的服務節點上,那麼勢必會影響性能,而其他的節點又很空閑,這樣就無法做到負載均衡。

關于master的配置設定有幾種政策。你可以在queue聲明的時候使用x-queue-master-locator參數,或者在policy上設定queue-master-locator,或者直接在rabbitmq的配置檔案中定義queue_master_locator。這裡有三種可供選擇的政策:

min-masters:選擇master數最少的那個服務節點

client-local:選擇與client相連接配接的那個服務節點

random:随機配置設定

當policy的ha-mode設定為nodes時,可以在指定清單中配置鏡像隊列,如果新配置或者修改的nodes清單中沒有目前的master,那麼勢必會造成資料的丢失。然而RabbitMQ會保持現有的master直到其他的鏡像至少有一個節點已經完全同步。但是如果發生同步的操作,隊列會出現假死的現象:consumer需要和master重建立立連接配接。

(譯者注:當調用同步指令後,隊列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會預設同步已知的鏡像隊列。由于同步過程的限制,是以不建議在生産的active隊列(有生産消費消息)中操作。)

舉例,queue在節點[A, B]中,并且A為master,此時設定節點的政策為在[C,D]中,那麼首先queue會存在在[A,C,D]中,等到完全同步之後,A會被shutdown,進而在[C,D]中選擇一個master。

當一個connection關閉的時候,其上鎖declare的排他(exclusive)queues将會被删除。對于一個排他隊列來說,為它設定鏡像隊列是沒有用的。

排他隊列是不能被鏡像的,也不能被持久化。

下面的例子是為所有以”ha.”開頭的隊列設定名為“ha-all”的policy。

mode

description

rabbitmqctl

rabbitmqctl set_policy ha-all “^ha.” ‘{“ha-mode”:”all”}’

rabbitmqctl(Windows)

rabbitmqctl set_policy ha-all “^ha.” “{“”ha-mode”“:”“all”“}”

HTTP API

PUT /api/policies/%2f/ha-all {“pattern”:”^ha.\”, “definition”:{“ha-mode”:”all”}}

Web UI

Navigate to Admin > Policies> Add/ update a policy. Enter “ha-all” next to Name, “^ha.” next to Pattern, and “ha-mode”=”all” in the first line next to Policy. Click Add policy.

為每個以“two.”開頭的隊列設定兩個節點的鏡像,并且設定為自動同步模式:

rabbitmqctl set_policy ha-two “^two.” ‘{“ha-mode”:”exactly”, “ha-params”:2, “ha-sync-mode”:”automatic”}’

rabbitmqctl (Windows)

rabbitmqctl set_policy ha-two “^two.” “{“”ha-mode”“:”“all”“,”“ha-params”“:2, “”ha-sync-mode”“:”“automatic”“}”

PUT /api/policies/%2f/ha-two {“pattern”:”^two.”,”definition”:{“ha-mode”:”exactly”,”ha-params”:2,”ha-sync-mode”:”automatic”}}

Navigate to Admin > Policies > Add /update a policy. Enter ‘ha-two’ next to Name and “^two.” next to Pattern. Enter “ha-mode”=”exactly” in the first line next to Policy, then “ha-params”=2 in the second line, then “ha-sync-mode”=”automatic” in the third, and the type on the second line to “Number”. Click Add policy.

為每個以“node.”開頭的隊列配置設定指定的節點做鏡像

rabbitmqctl set_policy ha-nodes “^nodes.” ‘{“ha-mode”:”nodes”,”ha-params”:[“rabbit@nodeA”,”rabbit@nodeB”]}’

rabbitmqctl set_policy ha-nodes “^nodes.” “{“”ha-mode”“:”“node”“,”“ha-params”“:[“”rabbit@nodeA”“,”“rabbit@nodeB”“]}”

PUT /api/policies/%2f/ha-nodes {“pattern”:”^node.”,”definition”:{“ha-mode”:”nodes”,”ha-params”:[“rabbit@nodeA”,”rabbit@nodeB”]}}

Navigate to Admin > Policies > Add / update a policy. Enter “ha-nodes” next to Name and “^nodes.” next to Pattern. Enter “ha-mode” = “nodes” in the first line next to Policy, then “ha-params” in the second line, set the second line’s type to “List”, and then enter “rabbit@nodeA” and “rabbit@nodeB” in the sublist which appears. Click Add policy.

正如先前所論述的,每個鏡像隊列中擁有一個master和多個slave,這些都分布在不同的節點上。在master上的操作會在slave上一樣的執行,這樣才能保持一緻的狀态。對于鏡像隊列,用戶端Basic.Publish操作會同步到所有節點(消息同時發送到master和所有slave上,如果此時master宕掉了,消息還發送slave上,這樣當slave提升為master的時候消息也不會丢失),而其他操作則是通過master中轉,再由master将操作作用于slave。比如一個Basic.Get操作,假如用戶端與slave建立了TCP連接配接,首先是slave将Basic.Get請求發送至master,由master備好資料,傳回至slave,投遞給消費者。

All actions other than publishes go only to the master, and the master then broadcasts the effect of the actions to the mirrors.

如果某個slave失效了,系統處理做些記錄外幾乎啥都不做:master依舊是master,用戶端不需要采取任何行動或者被通知slave已失效。注意slave的失效不會被立刻檢測出來,

如果master失效了,那麼slave中的一個必須被選中為master。此時會發生如下的情形:

被選中作為新的master的slave通常是最老的那個,因為最老的slave與前任master之間的同步狀态應該是最好的。然而,需要注意的是,如果存在沒有任何一個slave與master完全同步的情況,那麼前任master中未被同步的消息将會丢失。

slave節點認為目前所有的消費者都已經突然的disconnect了。它會requeue所有被發送到用戶端但沒有被ack的消息。這裡包括用戶端以及發送ack但是丢失在傳回broker的路上,或者master已經收到但是其他的slave沒有收到,這些消息都會被requeue。無論何種清理,新的master隻能requeue所有沒有被ack的消息。

消費端如果引入了Consumer Cancellation Notification,那麼當目前的queue挂掉的時候應該被通知到。

由于requeue的存在,用戶端當重新消費queue的時候,有可能将之前消費過的消息又順序的消費一遍。

當一個slave提升為master的時候,發送到目前鏡像隊列的消息将不會丢失(除非這個新的master緊接着挂了)。消息發送到一個slave的時候,将會被路由到master上,進而又被複制到所有的slave上。此時如果master挂了,消息将會繼續發送到slave上,當一個slave提升為master的時候,這些消息會被存入queue中。

用戶端如果在發送消息是采用了publisher confirm機制,那麼在消息發送和消息确認之間master挂掉(或者任何slave挂掉)都不會影響confirm機制的正确運作。

如果你在消費一個鏡像隊列的時候這是autoAck=true(用戶端不會進行消息确認),那麼消息有可能會丢失。broker中的消息一旦發送出去就會被立刻确認(被确認的消息不能再被消費,且broker内部線程會執行清理工作将此消息清除),如果與用戶端建立的連接配接突然中斷,那麼消息将會永遠丢失。是以為了確定消息不丢失,還是建議你在消費時将autoAck設定為false。

RabbitMQ的鏡像隊列同時支援publisher confirm和事務兩種機制。在事務機制中,隻有目前事務在全部鏡像queue中執行之後,用戶端才會收到Tx.CommitOk的消息。同樣的,在publisher confirm機制中,向publisher進行目前message确認的前提是該message被全部鏡像所接受了。

基于credit的算法來實作限制消息發送的速率。

若用戶端在消費的時候執行了參數x-cancel-on-ha-failover=true,那麼當在故障處理的時候将會停止消費,并且會受到一個”consumer cancellation notification”. 這樣消費需要重新發送Basic.Consume進而可以重新消費。

舉例:

一個節點可以在任何時候加入叢集之中。根據queue的配置,當新節點加入進來的時候,這個queue有可能在這個新的節點上添加一個鏡像,此時這個鏡像(slave)是空的,它不包含任何queue中已經存在的内容。新加入的鏡像可以收到生産者新發送過來的消息,其内容與其他鏡像的尾部保持一緻。随着queue中的消息被逐漸的消費,新加入的鏡像中“錯失”的消息逐漸減少,直到與其他鏡像保持一緻,既而就已經完全處于同步狀态。但是需要注意的是,上述的同步行為是基于用戶端的操作而觸發的。

是以新加入的鏡像并沒有提供額外的備援和可靠性保障,除非它能精确的同步。将新節點加入已存在的鏡像隊列是,預設情況下ha-sync-mode=manual,鏡像隊列中的消息不會主動同步到新節點,除非顯式調用同步指令。當調用同步指令後,隊列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會預設同步已知的鏡像隊列。由于同步過程的限制,是以不建議在生産的active隊列(有生産消費消息)中操作。

可以使用下面的指令來檢視那些slaves已經完成同步:

可以通過手動的方式同步一個queue:

同樣也可以取消某個queue的同步功能:

當然這些都可以通過management插件來設定。

如果你關閉了鏡像隊列中的master節點,那麼剩餘的鏡像中會選舉一個作為新的master節點(假設都處于同步的狀态)。如果你繼續關閉節點直到沒有多餘鏡像了,那麼此時隻有一個節點可用,這個節點也是master節點。如果這個鏡像隊列配置了持久化屬性(durable=true)。那麼當最後的節點重新開機之後,消息不會丢失。然後你再重新開機其他的節點,它們會陸續的加入到鏡像隊列中來。

然而,目前還沒有方法判斷一個重新加入的鏡像是否保持和master同步的狀态,是以每當一個節點加入或者重新加入(例如從網絡分區中恢複過來)鏡像隊列,之前儲存的隊列内容會被清空。

當所有slave都出在(與master)未同步狀态時,并且ha-promote-on-shutdown設定為when-synced(預設)時,如果master因為主動的原因停掉,比如是通過rabbitmqctl stop指令停止或者優雅關閉OS,那麼slave不會接管master,也就是此時鏡像隊列不可用;但是如果master因為被動原因停掉,比如VM或者OS crash了,那麼slave會接管master。這個配置項隐含的價值取向是保證消息可靠不丢失,放棄可用性。如果ha-promote-on-shutdown設定為always,那麼不論master因為何種原因停止,slave都會接管master,優先保證可用性。

(略。實際上是不知道這段要表達什麼gui。)

RabbitMQ 3.6.0引入了一個與鏡像隊列有關的參數:ha-sync-batch-size。可以批量的進行消息同步,進而非常可觀的提升同步處理的效率。之前的版本預設隻能同步一條消息。

關于ha-sync-batch-size的取值,你需要考慮一下幾個方面:

消息的平均大小

RabbitMQ節點間的網絡吞吐量

舉個例子,如果你需要每次同步50000條消息,每條消息平均大小為1KB,那麼ha-sync-batch-size設定為約49MB左右。你需要确tim保你的網絡在鏡像節點之間能夠支援這樣的吞吐。如果你批量發送一批消息所使用的時間大于net_ticktime,那麼叢集有可能認為發生了網絡分區。

如果一個queue正在同步,所有對于其他的queues的操作将會被阻塞。一個queue有可能因為同步而被阻塞幾分鐘,幾小時甚至幾天。

将新節點加入已存在的鏡像隊列是,預設情況下ha-sync-mode=manual,鏡像隊列中的消息不會主動同步到新節點,除非顯式調用同步指令。當調用同步指令後,隊列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會預設同步已知的鏡像隊列。由于同步過程的限制,是以不建議在生産的active隊列(有生産消費消息)中操作。

繼續閱讀