預設情況下,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隊列(有生産消費消息)中操作。