#作者:閆乾苓
文章目錄
- 概述
- 工作原理
- 1.節點之間的交互
- 2.消息復制
- 3.共識機制
- 4.選舉領導者
- 5.消息持久化
- 6.自動故障轉移
- 集群環境
- 節點管理
- 仲裁隊列增加集群節點
- 重新平衡仲裁隊列leader所在節點
- 仲裁隊列減少集群節點
- 副本管理
- add_member 在給定節點上添加仲裁隊列成員(副本)
- delete_member刪除給定節點上的仲裁隊列成員(副本)
- 節點高可用測試
本文使用目前官方推薦的quorum 類型的隊列如何進行高可用設置進行了測試及說明。
概述
仲裁隊列是一種現代隊列類型,它基于Raft 共識算法實現了持久的、復制的 FIFO 隊列。
旨在更加安全,并提供更簡單、定義明確的故障處理語義,用戶在設計和操作系統時應該更容易推理。
仲裁隊列和流現在取代了原始的、復制的鏡像經典隊列。鏡像經典隊列早已被棄用,并已從 RabbitMQ 4.x 中刪除。
仲裁隊列針對數據安全是首要任務的用例進行了優化。仲裁隊列應被視為復制隊列類型的默認選項。
工作原理
RabbitMQ的仲裁隊列通過使用Raft一致性算法、消息復制和持久化等技術,實現了高可用性的消息傳輸和數據存儲。這些機制共同確保了即使在節點故障或網絡異常等不利情況下,消息仍然能夠可靠地傳輸和存儲
1.節點之間的交互
在RabbitMQ的集群中,節點之間通過交換消息來進行狀態同步。當一個新節點加入或發生故障轉移時,其他節點會與該節點進行交互,以確保其狀態與集群保持同步。
2.消息復制
RabbitMQ使用消息復制技術來確保消息在集群中的可靠存儲。每個消息都會被復制到多個節點上,以防止在某些節點發生故障時數據丟失。這種復制機制為數據的高可用性提供了保障。
3.共識機制
RabbitMQ的仲裁隊列使用Raft一致性算法來實現共識機制。Raft是一種用于管理復制日志的一致性算法,它通過在集群中的節點之間達成共識來確保消息的可靠傳輸。當一個節點發送一條消息時,其他節點會驗證該消息的一致性,并確保其在整個集群中可靠傳輸。
4.選舉領導者
在RabbitMQ的仲裁隊列中,存在一個領導者節點和一個或多個副節點。領導者節點負責處理寫請求,而副節點則復制領導者的操作。當領導者節點發生故障時,副節點會通過Raft算法進行選舉,以選出一個新的領導者節點來繼續處理寫請求。
5.消息持久化
RabbitMQ中的仲裁隊列支持消息持久化,這意味著即使在節點重啟或崩潰的情況下,消息也不會丟失。通過將消息寫入磁盤并在副節點之間進行復制,仲裁隊列確保了消息的長期保存和可靠性。
6.自動故障轉移
如果領導者節點發生故障,RabbitMQ會自動將一個副節點提升為新的領導者節點。其他副節點則會與新的領導者節點進行同步,以確保集群的可用性和數據的一致性。這種自動故障轉移機制進一步提高了RabbitMQ仲裁隊列的高可用性。
集群環境
Rabbimq集群使用RabbitMQ Cluster Kubernetes Operator部署,使用nfs storageClass的pvc進行持久化存儲。
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:name: rabbitmq-cluster01namespace: rabbitmq-test
spec:replicas: 3image: rabbitmq:3.13.7-managementresources:requests:cpu: 500mmemory: 1Gilimits:cpu: 2000mmemory: 4Girabbitmq:additionalConfig: |cluster_partition_handling = pause_minoritydisk_free_limit.relative = 1.0collect_statistics_interval = 10000channel_max = 1000vm_memory_high_watermark_paging_ratio = 0.7total_memory_available_override_value = 4GBlog.file = /var/log/rabbitmq/rabbit.logpersistence:storageClassName: nfsstorage: "20Gi"affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: app.kubernetes.io/nameoperator: Invalues:- rabbitmq-cluster01topologyKey: kubernetes.io/hostnameservice:type: NodePort
節點管理
仲裁隊列增加集群節點
通過在指定節點上為所有匹配的隊列添加成員(副本)來增長仲裁隊列集群。
rabbitmq-queues [--node <node>] [--longnames] [--quiet] grow <node> <all | even> [--vhost-pattern <pattern>] [--queue-pattern <pattern>] [--membership <promotable|voter>]
<--node>
用于放置副本的節點名稱
<all | even>
為所有匹配的隊列或僅為成員計數為偶數的隊列添加成員
--queue-pattern
用于匹配隊列名稱的正則表達式
--vhost
匹配虛擬主機名的正則表達式
--membership
添加可晉升的非投票人(默認)或正式投票人
--errors-only
僅列出報告錯誤的隊列
為所有匹配的隊列,vhost為/,匹配所有queue 增加‘rbt04’副本節點
rabbitmq-queues grow "rabbit@rbt04" "all" --vhost-pattern "/" --queue-pattern ".*"
僅為副本數為偶數的隊列,vhost為/,匹配所有queue, 增加‘rbt05’副本節點
[root@rbt01 ~]# rabbitmq-queues grow "rabbit@rbt05" "even" --vhost-pattern "/" --queue-pattern
".*"
因使用了even參數,只匹配了偶數節點的queue-quorum-02
重新平衡仲裁隊列leader所在節點
在正在運行的集群節點之間重新平衡復制隊列的領導者
用法:
rabbitmq-queues [--node <node>] [--longnames] [--quiet] rebalance < all | classic | quorum | stream > [--vhost-pattern <pattern>] [--queue-pattern <pattern>]
<type>隊列類型,必須是以下之一:all、classic、quorum、stream
--queue-pattern <pattern>用于匹配隊列名稱的正則表達式
--vhost-pattern <pattern>匹配虛擬主機名的正則表達式
對vhost /下,所有的queue,進行重新平衡leader
[root@rbt01 ~]# rabbitmq-queues rebalance "all" --vhost-pattern "/" --queue-pattern ".*"
重新平衡后,3個隊列的leader 由原來的全部為rbt01,變成了rbt01,rbt03,rbt04
仲裁隊列減少集群節點
用法:
rabbitmq-queues [--node <node>] [--longnames] [--quiet] shrink <node> [--errors-only]
<node>從中刪除副本的節點名稱
--errors-only僅列出報告錯誤的隊列
[root@rbt01 ~]# rabbitmq-queues shrink rabbit@rbt04
Shrinking quorum queues on rabbit@rbt04...
vhost name size result
/ queue-quorum-03 2 ok
/ queue-quorum-01 4 ok
/ queue-quorum-02 4 ok[root@rbt01 ~]# rabbitmq-queues shrink rabbit@rbt05
Shrinking quorum queues on rabbit@rbt05...
vhost name size result
/ queue-quorum-01 3 ok
/ queue-quorum-02 3 ok
副本管理
聲明仲裁隊列時,必須在集群中啟動其初始副本數。默認情況下,要啟動的副本數最多為三個,集群中每個 RabbitMQ 節點一個。
三個節點是仲裁隊列的實際最小副本數。在節點數較多的 RabbitMQ 集群中,添加比仲裁更多的副本不會在仲裁隊列可用性方面帶來任何改進,但會消耗更多集群資源。
因此,仲裁隊列的推薦副本數是群集節點的仲裁數(但不少于三個)。這假設一個完整的群集至少有三個節點。
仲裁隊列的副本由管理員管理。當新節點添加到集群時,它將不托管仲裁隊列副本,除非管理員明確將其添加到仲裁隊列或仲裁隊列集的成員(副本)列表中。
當需要退役節點(從集群中永久刪除)時,必須將其從其當前托管副本的所有仲裁隊列的成員列表中明確刪除。
為了成功添加和刪除成員,集群中必須有一定數量的副本,因為集群成員資格的更改被視為隊列狀態的更改。
在執行涉及成員資格變更的維護操作時,需要小心不要因失去法定人數而意外導致隊列不可用。
更換集群節點時,先添加新節點然后再退役其替換的節點是更安全的做法。
用于隊列(尤其是仲裁隊列)的維護任務, 管理復制隊列的副本
add_member 在給定節點上添加仲裁隊列成員(副本)
rabbitmq-queues add_member --vhost / queue-quorum-01 rabbit@rbt04
rabbitmq-queues add_member --vhost / queue-quorum-01 rabbit@rbt05
delete_member刪除給定節點上的仲裁隊列成員(副本)
rabbitmq-queues delete_member --vhost / queue-quorum-01 rabbit@rbt01
rabbitmq-queues delete_member --vhost / queue-quorum-01 rabbit@rbt02
節點高可用測試
創建queue
rabbitmqadmin declare queue name=queue_quorum_01 durable=true arguments='{"x-queue-type": "quorum"}'
創建exchange及bingding
rabbitmqadmin declare exchange name=exchange_quorum_01 type=direct durable=true
rabbitmqadmin declare binding source=exchange_quorum_01 destination=queue_quorum_01 routing_key=queue_quorum_01
查看創建的queue_quorum_01的狀態
查看exchange是否創建成功
通過python腳本寫入10000條消息數據。
手動刪除1個pod節點,模擬3節點集群中1個節點宕機的故障,數據不會丟失。
繼續在producer客戶端和cunsume 客戶端python腳本一直時運行時,進行刪除pod節點測試。
只要客戶端連接的不是被停止的pod節點,客戶端生產和消費都是正常的。通過web管理界面看,隊列的狀態是:running
此時如果繼續刪除第2個pod節點,模擬3節點集群中2個節點宕機的故障,在k8s中使用operator部署的RabbitMQ集群,在手動執行刪除第2個pod是,命令將被掛起(無反應)直到operator通過內容部控制機制將第1個刪除的pod重啟成功,才會繼續執行第2個pod的刪除操作。
此時Rabbimq服務是正常狀態(如果客戶端連接的是被刪除pod節點,連接會被斷開,重連后連接被svc 負載到其他pod節點,可以正常讀寫數據)。這應該是RabbitMQ operator控制的效果,在3節點的集群中,確保同時只能1個pod節點宕機,服務不受影響。
在此期間,消息隊列中的數據不會丟失。
另外在裸金屬部署的3節點RabbitMQ集群中進行了類似測試,使用“rabbitmq stop_app”同時停止2個節點(非讀寫客戶端正在連接的節點),此時RabbitMQ處于“minority”(少數)狀態,這正是quorum隊列需要超過半數節點正常才能正常工作的工作機制。
此時,寫客戶端(producer)的連接狀態雖然為‘running’但實際測試是沒有數據寫入到服務器,讀客戶端(consumer)的連接狀態為“flow”,也不能從服務器獲取數據。