title: 【MQ】鏡像隊(duì)列
date: 2017-12-08 21:57:51
tags: MQ
categories: MQ
前面提到持久化和消息確認(rèn)可以確保消息的可靠,但在默認(rèn)情況下 MQ 的可靠性完全沒(méi)有保障毁习,通過(guò)集群的方式確保服務(wù)的可靠往往是高可用的第一步。本文記錄一下 Rabbit MQ 的集群和鏡像俏险。
集群
集群能夠帶來(lái)的好處主要有兩點(diǎn):
- 允許消費(fèi)者和生產(chǎn)者在 Rabbit MQ 節(jié)點(diǎn)崩潰的情況下繼續(xù)運(yùn)行
- 通過(guò)添加更多節(jié)點(diǎn)線性的擴(kuò)展消息通信吞吐量
模式
在介紹集群之前先看看從單節(jié)點(diǎn)到集群的模式異同:
相同:任何模式下節(jié)點(diǎn)內(nèi)部都需要維護(hù)基本元數(shù)據(jù)信息:隊(duì)列元數(shù)據(jù)仇轻、交換器元數(shù)據(jù)、綁定元數(shù)據(jù)焦人、vhost 元數(shù)據(jù)挥吵。不同模式
-
差異:
-
單一節(jié)點(diǎn)模式:
默認(rèn)基本元數(shù)據(jù)信息存儲(chǔ)在內(nèi)存,被標(biāo)記持久化的隊(duì)列和交換器已經(jīng)它們的綁定存儲(chǔ)到磁盤
-
普通集群模式
cluster.PNGcluster.PNG除了基本元數(shù)據(jù)花椭,還有集群相關(guān)元數(shù)據(jù)忽匈。與單一節(jié)點(diǎn)模式的不同主要在集群對(duì)隊(duì)列,交換器矿辽,數(shù)據(jù)存儲(chǔ)的差異丹允。
隊(duì)列:只會(huì)在單個(gè)節(jié)點(diǎn)而不是所有節(jié)點(diǎn)上創(chuàng)建完整隊(duì)列信息郭厌,其余節(jié)點(diǎn)只保存隊(duì)列的元數(shù)據(jù)。雖然只在一個(gè)節(jié)點(diǎn)保存完整隊(duì)列雕蔽,但消息可以在不同節(jié)點(diǎn)之間臨時(shí)傳輸(消費(fèi)者感知到每個(gè)節(jié)點(diǎn)都有完整的隊(duì)列)折柠。如果保存隊(duì)列的單個(gè)節(jié)點(diǎn)掛了,則消費(fèi)者對(duì)其訂閱丟失批狐,即將投遞到該隊(duì)列的信息消息也丟失液走。如果掛掉的隊(duì)列是持久化隊(duì)列則無(wú)法重新創(chuàng)建隊(duì)列,必須恢復(fù)該隊(duì)列
交換器:交換器實(shí)質(zhì)是一張查詢表(消息的轉(zhuǎn)發(fā)路由是由信道完成)贾陷,集群內(nèi)所有的節(jié)點(diǎn)擁有所有交換器的信息
數(shù)據(jù)存儲(chǔ):分為內(nèi)存節(jié)點(diǎn)和硬盤節(jié)點(diǎn)缘眶,硬盤節(jié)點(diǎn)防止重啟后元數(shù)據(jù)信息丟失。元數(shù)據(jù)的創(chuàng)建更新在集群所有節(jié)點(diǎn)操作完成后才返回髓废。集群下要求任何時(shí)刻集群中至少有一個(gè)磁盤節(jié)點(diǎn)巷懈,如果唯一的磁盤節(jié)點(diǎn)掛了,集群只能路由消息但不能創(chuàng)建更新元數(shù)據(jù)
-
鏡像隊(duì)列
image_queues.PNG鏡像不在單獨(dú)存在在唯一節(jié)點(diǎn)慌洪,而是冗余在多個(gè)節(jié)點(diǎn)
-
鏡像隊(duì)列
因?yàn)槠胀耗J较鄬?duì)基礎(chǔ)顶燕,而鏡像隊(duì)列復(fù)雜,這里重點(diǎn)討論一下鏡像隊(duì)列冈爹。
概述
隊(duì)列鏡像通常包括一個(gè) master 節(jié)點(diǎn)和多個(gè) slave 節(jié)點(diǎn)涌攻,每個(gè)節(jié)點(diǎn)都復(fù)制隊(duì)列數(shù)據(jù)。當(dāng)一個(gè)節(jié)點(diǎn)失效時(shí)频伤,可以自動(dòng)切換到另一個(gè)節(jié)點(diǎn)確笨一眩可用。在鏡像隊(duì)列模式下憋肖,除了 publish 外的所有動(dòng)作都只會(huì)向 master 發(fā)送因痛,然后 master 將命令執(zhí)行的結(jié)果廣播為所有 slave,publish 到鏡像的所有消息總是被直接 publish 到所有 slave 之上(類似與 fanout 交換器)岸更。
原理
普通隊(duì)列結(jié)構(gòu)
普通隊(duì)列由兩部分組成:
- AMQQueue:主要負(fù)責(zé) AMQP 協(xié)議的邏輯功能
- BackingQueue:存儲(chǔ)消息
對(duì)于 BackingQueue 又由五個(gè)子隊(duì)列組成:Q1, Q1, Delta, Q3, Q4鸵膏,MQ 的消息進(jìn)入隊(duì)列后會(huì)隨著系統(tǒng)負(fù)載在隊(duì)列中流動(dòng),BackingQueue 中的消息可以分為四個(gè)狀態(tài):
- Alpha:消息的內(nèi)容和索引都在內(nèi)存中怎炊,Q1 和 Q4 的狀態(tài)
- Beta:消息的內(nèi)容在硬盤谭企,消息的索引在內(nèi)存,Q2 和 Q3 的狀態(tài)
- Gamma:消息內(nèi)容在硬盤评肆,消息的索引在硬盤和內(nèi)存都有债查,Q2 和 Q3 的狀態(tài)
- Delta:消息的內(nèi)容和消息的索引都在硬盤上,Delta 狀態(tài)
對(duì)于持久化的消息糟港,消息內(nèi)容和消息索引都必須先保存到磁盤上攀操,才會(huì)處于上述狀態(tài)中的一種,而Gamma狀態(tài)的消息只有持久化的消息才會(huì)有該狀態(tài)秸抚。
從 Q1 到 Q4速和,基本的經(jīng)歷是由內(nèi)存到硬盤再到內(nèi)存的設(shè)計(jì)歹垫,分層的好處使得整個(gè)隊(duì)列有很好的彈性:
- 當(dāng)隊(duì)列負(fù)載很高的情況下,能夠通過(guò)將一部分消息由磁盤保存來(lái)節(jié)省內(nèi)存空間
- 當(dāng)負(fù)載降低的時(shí)候颠放,這部分消息又漸漸回到內(nèi)存排惨,被消費(fèi)者獲取
引起消息流動(dòng)的兩種情況:消費(fèi)者獲取消息,內(nèi)存不足
當(dāng)系統(tǒng)處于正常負(fù)載碰凶,對(duì)消息的消費(fèi)速度不小于接收速度暮芭,對(duì)于非消息極可能只會(huì)有 Alpha 狀態(tài)。對(duì)于持久化消息一定會(huì)進(jìn)入 gamma 狀態(tài)欲低。如果開啟 confirm 機(jī)制辕宏,只有到了這個(gè)階段才會(huì)確認(rèn)消息已經(jīng)被接受,當(dāng)消費(fèi)足夠快且內(nèi)存充足消息不會(huì)繼續(xù)走到下一狀態(tài)砾莱。
當(dāng)系統(tǒng)處于高負(fù)載瑞筐,已接受的消息不能很快被消費(fèi),這些消息就會(huì)進(jìn)入很深的隊(duì)列中去腊瑟,增加處理每個(gè)消息的平均開銷聚假。因?yàn)槠骄_銷增加,處理速度更慢闰非,由此惡性循環(huán)膘格,使得系統(tǒng)的處理能力大大降低。
改善措施:
- 進(jìn)行流程控制
- 增加 prefetch 的值财松,一次發(fā)送更多消息給消費(fèi)者
- 采用 multiple ack
鏡像隊(duì)列結(jié)構(gòu)
在鏡像隊(duì)列中 AMQQueue 仍舊負(fù)責(zé) AMQP 協(xié)議的邏輯功能瘪贱,而 backing_queue 已不是簡(jiǎn)單的單節(jié)點(diǎn) backing_queue 了。
backing_queue 是由 master 和 slave 節(jié)點(diǎn)組成的特殊 backing_queue游岳,所有對(duì) mirror_queue_master 的操作政敢,會(huì)通過(guò) GM 同步到 slave 節(jié)點(diǎn)其徙,slave 節(jié)點(diǎn)上 mirror_queue_slave 負(fù)責(zé)回調(diào)胚迫,master 節(jié)點(diǎn)上 coordinato 負(fù)責(zé)回調(diào)。
鏡像隊(duì)列對(duì)消息的操作:
- basic.publish 操作:操作直接同步到所有節(jié)點(diǎn)
- 其他操作:通過(guò) master 操作唾那,由 master 將結(jié)果給 slave
GM
GM(Guarenteed Multicast)访锻,實(shí)現(xiàn)可靠組播通訊協(xié)議的模塊,確保組播消息的原子性:
- 將所有節(jié)點(diǎn)形成一個(gè)收尾相連的循環(huán)鏈表
- 當(dāng)有節(jié)點(diǎn)新增時(shí)闹获,相鄰的節(jié)點(diǎn)保證當(dāng)前廣播的消息會(huì)復(fù)制到新的節(jié)點(diǎn)上
- 當(dāng)有節(jié)點(diǎn)失效時(shí)期犬,相鄰的節(jié)點(diǎn)會(huì)接管保證本次廣播的消息會(huì)復(fù)制到所有節(jié)點(diǎn)
- 消息從master節(jié)點(diǎn)對(duì)應(yīng)的gm發(fā)出后,順著鏈表依次傳送到所有節(jié)點(diǎn)
鏡像隊(duì)列細(xì)節(jié)備忘
鏡像隊(duì)列細(xì)節(jié)太多避诽,這里整理網(wǎng)上一個(gè)注意事項(xiàng):
鏡像隊(duì)列不能作為負(fù)載均衡使用龟虎,因?yàn)槊總€(gè)操作在所有節(jié)點(diǎn)都要做一遍
ha-mode 參數(shù)與 durable, declare 對(duì) exclusive 隊(duì)列都不生效。exclusive隊(duì)列是連接獨(dú)占的沙庐,當(dāng)連接斷開鲤妥,隊(duì)列自動(dòng)刪除佳吞,這兩個(gè)參數(shù)對(duì)exclusive隊(duì)列沒(méi)有意義
將新節(jié)點(diǎn)加入已存在的鏡像隊(duì)列時(shí),默認(rèn)情況下 ha-sync-mode=manual棉安,鏡像隊(duì)列中的消息不會(huì)主動(dòng)同步到新節(jié)點(diǎn)底扳,除非顯式調(diào)用同步命令。當(dāng)調(diào)用同步命令后贡耽,隊(duì)列開始阻塞衷模,無(wú)法對(duì)其進(jìn)行操作,直到同步完畢蒲赂。當(dāng) ha-sync-mode=automatic 時(shí)阱冶,新加入節(jié)點(diǎn)時(shí)會(huì)默認(rèn)同步已知的鏡像隊(duì)列。由于同步過(guò)程的限制滥嘴,所以不建議在生產(chǎn)環(huán)境的active隊(duì)列(有生產(chǎn)消費(fèi)消息)中操作
每當(dāng)一個(gè)節(jié)點(diǎn)加入或者重新加入(例如從網(wǎng)絡(luò)分區(qū)中恢復(fù)回來(lái))鏡像隊(duì)列熙揍,之前保存的隊(duì)列內(nèi)容會(huì)被清空
鏡像隊(duì)列有主從之分,一個(gè)主節(jié)點(diǎn)(master)氏涩,0個(gè)或多個(gè)從節(jié)點(diǎn)(slave)届囚。當(dāng) master 宕掉后,會(huì)在 slave中 選舉新的master是尖。選舉算法為最早啟動(dòng)的節(jié)點(diǎn)
-
當(dāng)所有slave都處在(與master)未同步狀態(tài)時(shí)意系,并且 ha-promote-on-shutdown policy 設(shè)置為 when-syned(默認(rèn)) 時(shí),如果 master 因?yàn)橹鲃?dòng)的原因停掉饺汹,比如是通過(guò) rabbitmqctl stop 命令停止或者優(yōu)雅關(guān)閉 OS蛔添,那么slave不會(huì)接管 master,也就是說(shuō)此時(shí)鏡像隊(duì)列不可用
但是如果master因?yàn)楸粍?dòng)原因停掉兜辞,比如 VM 或者 OS crash了迎瞧,那么 slave 會(huì)接管 master。這個(gè)配置項(xiàng)隱含的價(jià)值取向是優(yōu)先保證消息可靠不丟失逸吵,放棄可用性凶硅。
如果 ha-promote-on-shutdown policy 設(shè)置為 alway,那么不論 master 因?yàn)楹畏N原因停止扫皱,slave 都會(huì)接管 master足绅,優(yōu)先保證可用性
-
鏡像隊(duì)列中最后一個(gè)停止的節(jié)點(diǎn)會(huì)是 master,啟動(dòng)順序必須是 master 先起韩脑,如果 slave 先起氢妈,它會(huì)有 30 秒的等待時(shí)間,等待 master 啟動(dòng)段多,然后加入 cluster首量。
當(dāng)所有節(jié)點(diǎn)因故(斷電等)同時(shí)離線時(shí),每個(gè)節(jié)點(diǎn)都認(rèn)為自己不是最后一個(gè)停止的節(jié)點(diǎn)。要恢復(fù)鏡像隊(duì)列加缘,可以嘗試在 30 秒之內(nèi)同時(shí)啟動(dòng)所有節(jié)點(diǎn)
對(duì)于鏡像隊(duì)列粥航,客戶端basic.publish操作會(huì)同步到所有節(jié)點(diǎn);而其他操作則是通過(guò)master中轉(zhuǎn)生百,再由master將操作作用于salve递雀。比如一個(gè)basic.get操作,假如客戶端與slave建立了TCP連接蚀浆,首先是slave將basic.get請(qǐng)求發(fā)送至master缀程,由master備好數(shù)據(jù),返回至slave市俊,投遞給消費(fèi)者
-
當(dāng) slave 宕掉時(shí)杨凑,除了與 slave 相連的客戶端連接全部斷開之外,沒(méi)有其他影響摆昧。
當(dāng) master 宕掉時(shí)撩满,會(huì)有以下連鎖反應(yīng):
- 與 master 相連的客戶端連接全部斷開。
- 選舉最老的 slave 為 master绅你。若此時(shí)所有 slave 處于未同步狀態(tài)伺帘,則未同步部分消息丟失。
- 新的 master 節(jié)點(diǎn) requeue 所有 unack 消息忌锯,因?yàn)檫@個(gè)新節(jié)點(diǎn)無(wú)法區(qū)分這些 unack 消息是否已經(jīng)到達(dá)客戶端伪嫁,亦或是 ack 消息丟失在到老master的通路上,亦或是丟在老 master 組播 ack 消息到所有 slave 的通路上偶垮。所以處于消息可靠性的考慮张咳,requeue 所有 unack 的消息。此時(shí)客戶端可能受到重復(fù)消息似舵。
- 如果客戶端連著 slave脚猾,并且 basic.consume 消息時(shí)指定了x-cancel-on-ha-failover參數(shù),那么客戶端會(huì)收到一個(gè) Consumer Cancellation Notification 通知砚哗,Java SDK中會(huì)回調(diào) Consumer 接口的handleCancel() 方法龙助,故需覆蓋此方法。如果不指定 x-cancel-on-ha-failover 參數(shù)频祝,那么消費(fèi)者就無(wú)法感知 master 宕機(jī)泌参,會(huì)一直等待下去
鏡像隊(duì)列的恢復(fù)
前提:兩個(gè)節(jié)點(diǎn) A 和 B 組成以鏡像隊(duì)列
-
場(chǎng)景一:A 先停,B 后停
該場(chǎng)景下 B 是 master常空,只要先啟動(dòng) B,再啟動(dòng) A 即可盖溺±觳冢或者先啟動(dòng) A,再在 30s 之內(nèi)啟動(dòng) B 即可恢復(fù)鏡像隊(duì)列烘嘱。如果沒(méi)有在 30s 內(nèi)恢復(fù) B昆禽,那么 A 自己就停掉自己
-
場(chǎng)景二:A蝗蛙,B 同時(shí)停
該場(chǎng)景可能是由掉電等原因造成,只需在 30s 之內(nèi)連續(xù)啟動(dòng) A 和 B 即可恢復(fù)鏡像隊(duì)列
-
場(chǎng)景三:A 先停醉鳖,B 后停捡硅,且 A 無(wú)法恢復(fù)
因?yàn)?B 是 master,所以等 B 起來(lái)后盗棵,在 B 節(jié)點(diǎn)上調(diào)用 rabbitmqctl forget_cluster_node A 以解除 A 的 cluster 關(guān)系壮韭,再將新的 slave 節(jié)點(diǎn)加入 B 即可重新恢復(fù)鏡像隊(duì)列
-
場(chǎng)景四:A 先停,B 后停纹因,且 B 無(wú)法恢復(fù)
此時(shí) B 是 master喷屋,所以直接啟動(dòng) A 是不行的,當(dāng) A 無(wú)法啟動(dòng)時(shí)瞭恰,也就沒(méi)辦法在 A 節(jié)點(diǎn)上調(diào)用 rabbitmqctl forget_cluster_node B屯曹。新版本中,forget_cluster_node 支持 --offline 參數(shù)惊畏,offline 參數(shù)允許 rabbitmqctl 在離線節(jié)點(diǎn)上執(zhí)行 forget_cluster_node 命令恶耽,迫使 RabbitMQ 在未啟動(dòng)的 slave 節(jié)點(diǎn)中選擇一個(gè)作為 master。當(dāng)在 A 節(jié)點(diǎn)執(zhí)行 rabbitmqctl forget_cluster_node --offline B 時(shí)颜启,RabbitMQ 會(huì) mock 一個(gè)節(jié)點(diǎn)代表 A驳棱,執(zhí)行 forget_cluster_node 命令將 B 剔出 cluster,然后 A 就能正常啟動(dòng)了农曲。最后將新的 slave 節(jié)點(diǎn)加入 A 即可重新恢復(fù)鏡像隊(duì)列
-
場(chǎng)景五:A 先停社搅,B 后停,且 A 和 B 均無(wú)法恢復(fù)乳规,但是能得到 A 或 B 的磁盤文件
這個(gè)場(chǎng)景更加難以處理形葬。將A或B的數(shù)據(jù)庫(kù)文件($RabbitMQ_HOME/var/lib目錄中)copy至新節(jié)點(diǎn)C的目錄下,再將 C 的 hostname 改成 A 或者 B 的 hostname暮的。如果 copy 過(guò)來(lái)的是 A 節(jié)點(diǎn)磁盤文件笙以,按場(chǎng)景四處理,如果拷貝過(guò)來(lái)的是 B 節(jié)點(diǎn)的磁盤文件冻辩,按場(chǎng)景三處理猖腕。最后將新的 slave 節(jié)點(diǎn)加入 C 即可重新恢復(fù)鏡像隊(duì)列
-
場(chǎng)景六:A 先停,B 后停恨闪,且 A 和 B 均無(wú)法恢復(fù)倘感,且無(wú)法得到 A 和 B 的磁盤文件
跑路吧
參考: