Highly Available (Mirrored) Queues
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
本文翻譯RabbitMQ官方文檔:Highly Available (Mirrored) Queues,原文地址:http://www.rabbitmq.com/ha.html。(翻譯水平有限,不喜輕噴~~)
高可用(鏡像)隊列
默認情況下,queues存放在RabbitMQ集群的單個節點之上。exchanges和bindings恰恰相反,在集群中的所有節點中都有存檔。queues可以配置鏡像以此可以在多個節點中有備份。每個鏡像隊列包含一個master節點和一個或者多個slave節點。如果master節點由于某種原因失效,那么“資歷最老”的slave節點將被提升為新的master節點。(譯者注:根據進入的時間排序,時間最長的節點即為資歷最老的節點。)
發送的消息將被復制到隊列的所有鏡像節點。Consumer連接的是master節點,而不是其他的slave節點(譯者注:就算程序連接的是slave節點,也會路由到master節點,然后建立tcp連接),被消費并被確認的消息將會被清除。鏡像隊列增強了其可用性,但是并沒有分攤負載。
這里并不推薦在局域網之外搭建RabbitMQ集群,這樣會有網絡分區的風險。同時,客戶端程序最好也部署在相同的局域網之內。
How Mirroring is Configured
在我們演示怎么配置鏡像隊列之前先看下之前是怎么使用的,然后我們再來陳述現在推薦怎么使用。
除了mandatory這個屬性(亦或者durable,exclusive)之外,RabbitMQ中的queue也有可選的參數(parameters, 也可以稱之為arguments), 有時會涉及到類似x-arguments這個參數。x-arguments可以用來配置鏡像參數,但是現在又更好的辦法,那就是通過policies(策略)。
Queue Arguments that Control Mirroring
Policies可以在任何適合改變,比如期初你創建了一個無鏡像的queue,在之后的某個節點你可以為這個queue配置鏡像,反之亦然。對于沒有配置鏡像的queue(non-mirrored)和配置了鏡像但是又沒有slave鏡像節點的queue之間是有區別的,前者卻反了額外的鏡像機制,但是可以有更高的吞吐量。
通過policy創建對象,這里包括了兩個關鍵的參數:ha-mode和ha-params(可選)。下表做了相應的解釋
| 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,造成數據丟失。)
To How Many Nodes to Mirror?
ha-mode:all是一種非常保守且不必要選擇。在有三個或者更多節點的集群中推薦配置大多數個數即可. 比如3個幾點的集群配置為2, 或者5個節點的集群配置為3。有些數據是瞬時性的,對延遲要求比較高,可以配置更少的鏡像個數,甚至可以不配置鏡像。
Queue Masters, Master Migration, Data Locality
Queue Master Location
每個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:隨機分配
“nodes" Policy and Migrating Masters
當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。
Exclusive Queues
當一個connection關閉的時候,其上鎖declare的排他(exclusive)queues將會被刪除。對于一個排他隊列來說,為它設置鏡像隊列是沒有用的。
排他隊列是不能被鏡像的,也不能被持久化。
Examples
下面的例子是為所有以"ha."開頭的隊列設置名為“ha-all"的policy。
| 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 | 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”"}" |
| HTTP API | PUT /api/policies/%2f/ha-two {“pattern”:"^two.",“definition”:{“ha-mode”:“exactly”,“ha-params”:2,“ha-sync-mode”:“automatic”}} |
| Web UI | 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 | rabbitmqctl set_policy ha-nodes “^nodes.” ‘{“ha-mode”:“nodes”,“ha-params”:[“rabbit@nodeA”,“rabbit@nodeB”]}’ |
| rabbitmqctl (Windows) | rabbitmqctl set_policy ha-nodes “^nodes.” “{”“ha-mode”":"“node”","“ha-params”":["“rabbit@nodeA”","“rabbit@nodeB”"]}" |
| HTTP API | PUT /api/policies/%2f/ha-nodes {“pattern”:"^node.",“definition”:{“ha-mode”:“nodes”,“ha-params”:[“rabbit@nodeA”,“rabbit@nodeB”]}} |
| Web UI | 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. |
Mirrored Queue Implementation and Semantics
正如先前所論述的,每個鏡像隊列中擁有一個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。此時會發生如下的情形:
如果你在消費一個鏡像隊列的時候這是autoAck=true(客戶端不會進行消息確認),那么消息有可能會丟失。broker中的消息一旦發送出去就會被立刻確認(被確認的消息不能再被消費,且broker內部線程會執行清理工作將此消息清除),如果與客戶端建立的連接突然中斷,那么消息將會永遠丟失。所以為了確保消息不丟失,還是建議你在消費時將autoAck設置為false。
Publisher Confirms and Transactions
RabbitMQ的鏡像隊列同時支持publisher confirm和事務兩種機制。在事務機制中,只有當前事務在全部鏡像queue中執行之后,客戶端才會收到Tx.CommitOk的消息。同樣的,在publisher confirm機制中,向publisher進行當前message確認的前提是該message被全部鏡像所接受了。
Flow Control
基于credit的算法來實現限制消息發送的速率。
Master Failures and Consumer Cancellation
若客戶端在消費的時候執行了參數x-cancel-on-ha-failover=true,那么當在故障處理的時候將會停止消費,并且會受到一個"consumer cancellation notification". 這樣消費需要重新發送Basic.Consume進而可以重新消費。
舉例:
Channel channel = ...; Consumer consumer = ...; Map<String, Object> args = new HashMap<String, Object>(); args.put("x-cancel-on-ha-failover", true); channel.basicConsume("my-queue", false, args, consumer);Unsynchronised Mirrors
一個節點可以在任何時候加入集群之中。根據queue的配置,當新節點加入進來的時候,這個queue有可能在這個新的節點上添加一個鏡像,此時這個鏡像(slave)是空的,它不包含任何queue中已經存在的內容。新加入的鏡像可以收到生產者新發送過來的消息,其內容與其他鏡像的尾部保持一致。隨著queue中的消息被逐漸的消費,新加入的鏡像中“錯失”的消息逐漸減少,直到與其他鏡像保持一致,既而就已經完全處于同步狀態。但是需要注意的是,上述的同步行為是基于客戶端的操作而觸發的。
所以新加入的鏡像并沒有提供額外的冗余和可靠性保障,除非它能精確的同步。將新節點加入已存在的鏡像隊列是,默認情況下ha-sync-mode=manual,鏡像隊列中的消息不會主動同步到新節點,除非顯式調用同步命令。當調用同步命令后,隊列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會默認同步已知的鏡像隊列。由于同步過程的限制,所以不建議在生產的active隊列(有生產消費消息)中操作。
可以使用下面的命令來查看那些slaves已經完成同步:
rabbitmqctl list_queues name slave_pids synchronised_slave_pids可以通過手動的方式同步一個queue:
rabbitmqctl sync_queue name同樣也可以取消某個queue的同步功能:
rabbitmqctl cancel_sync_queue name當然這些都可以通過management插件來設置。
Stopping nodes and synchronisation
如果你關閉了鏡像隊列中的master節點,那么剩余的鏡像中會選舉一個作為新的master節點(假設都處于同步的狀態)。如果你繼續關閉節點直到沒有多余鏡像了,那么此時只有一個節點可用,這個節點也是master節點。如果這個鏡像隊列配置了持久化屬性(durable=true)。那么當最后的節點重啟之后,消息不會丟失。然后你再重啟其他的節點,它們會陸續的加入到鏡像隊列中來。
然而,目前還沒有方法判斷一個重新加入的鏡像是否保持和master同步的狀態,因此每當一個節點加入或者重新加入(例如從網絡分區中恢復過來)鏡像隊列,之前保存的隊列內容會被清空。
Stopping Master Nodes with Only Unsynchronised Mirrors
當所有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,優先保證可用性。
Loss of Master While All Mirrors are Stopped
(略。實際上是不知道這段要表達什么gui。)
Batch Synchronization
RabbitMQ 3.6.0引入了一個與鏡像隊列有關的參數:ha-sync-batch-size。可以批量的進行消息同步,進而非常可觀的提升同步處理的效率。之前的版本默認只能同步一條消息。
關于ha-sync-batch-size的取值,你需要考慮一下幾個方面:
- 消息的平均大小
- RabbitMQ節點間的網絡吞吐量
- net_ticktime的值(參考:http://www.rabbitmq.com/nettick.html)
舉個例子,如果你需要每次同步50000條消息,每條消息平均大小為1KB,那么ha-sync-batch-size設置為約49MB左右。你需要確tim保你的網絡在鏡像節點之間能夠支持這樣的吞吐。如果你批量發送一批消息所使用的時間大于net_ticktime,那么集群有可能認為發生了網絡分區。
Configuring Synchronisation
如果一個queue正在同步,所有對于其他的queues的操作將會被阻塞。一個queue有可能因為同步而被阻塞幾分鐘,幾小時甚至幾天。
將新節點加入已存在的鏡像隊列是,默認情況下ha-sync-mode=manual,鏡像隊列中的消息不會主動同步到新節點,除非顯式調用同步命令。當調用同步命令后,隊列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會默認同步已知的鏡像隊列。由于同步過程的限制,所以不建議在生產的active隊列(有生產消費消息)中操作。
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
總結
以上是生活随笔為你收集整理的Highly Available (Mirrored) Queues的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kafka端到端审计
- 下一篇: RabbitMQ的元数据重建