RabbitMQ 的延时队列和镜像队列原理与实战
在阿里云棲開發(fā)者沙龍PHP技術(shù)專場(chǎng)上,掌閱資深后端工程師、掘金小測(cè)《Redis深度歷險(xiǎn)》作者錢文品為大家介紹了RabbitMQ的延時(shí)隊(duì)列和鏡像隊(duì)列的原理與實(shí)踐,重點(diǎn)比較了RabbitMQ提供的消息可靠與不可靠模式,同時(shí)介紹了生產(chǎn)環(huán)境下如何使用RabbitMQ實(shí)現(xiàn)集群間消息傳輸。
本次直播視頻精彩回顧,戳這里!
直播回顧:https://yq.aliyun.com/live/965
PPT分享:https://yq.aliyun.com/download/3529
本文根據(jù)演講視頻以及PPT整理而成。
本文將主要圍繞以下四個(gè)方面進(jìn)行分享:
RabbitMQ特性
對(duì)于左邊的Client Publisher而言,RabbitMQ Server是消息的接收者,也就是消費(fèi)者;對(duì)于右邊的Client Consumer而言,RabbitMQ Server是消息的發(fā)送者,也就是生產(chǎn)者。RabbitMQ Server將消息從Client Publisher傳送給Client Consumer,扮演著消息中間商的角色。
RabbitMQ Server負(fù)責(zé)將Client Publisher傳遞來的消息持久化,延后地將消息傳遞給Client Consumer.這樣,即使消費(fèi)者掛掉,RabbitMQ Server也可以存儲(chǔ)消息,當(dāng)消費(fèi)者重新工作時(shí)再將存儲(chǔ)的消息傳遞過去,從而保證消息不丟失。RabbitMQ Server提供了堆積消息的能力。
?
另外,RabbitMQ Server還具有復(fù)制和廣播消息的能力。具體來說,RabbitMQ Server可以將Client Publisher發(fā)布的消息分發(fā)給多個(gè)消費(fèi)者,比如它能夠?qū)⑻囟ǖ南凑仗囟ǖ年?duì)列分發(fā)給特定的消費(fèi)者。“特定”指不同消息具有不同的routing key屬性,由上圖實(shí)例,不同的消息生產(chǎn)者生產(chǎn)了具有不同routing key的消息,通過exchange路由器將不同的routing key消息投遞到不同隊(duì)列,從而分發(fā)給不同消費(fèi)者。
RabbitMQ中的消息不可靠問題及其解決方案
消費(fèi)端消息不可靠問題及其解決方案
?
實(shí)際上,RabbitMQ Server將消息投遞給消費(fèi)者,具有消息不可靠的特點(diǎn)。具體來說,RabbitMQ Server將消息投遞給消費(fèi)者時(shí)會(huì)調(diào)用套接字的write操作,而write操作的過程是不可靠性的。在write操作的過程中,Server需要將消息發(fā)送到套接字的緩存中,通過網(wǎng)卡轉(zhuǎn)發(fā)到鏈路上,最終到達(dá)消費(fèi)者所在的機(jī)器內(nèi)核的套接字緩存中,由消費(fèi)者使用套接字的read操作將消息讀出來。
即使套接字的write操作成功也無法保證消息可靠,潛在的網(wǎng)絡(luò)故障可能使消費(fèi)者接收不到消息。機(jī)器宕機(jī)也可能使消息不可靠,即使消息字節(jié)流已經(jīng)到達(dá)消費(fèi)者所在機(jī)器,消費(fèi)者所在機(jī)器的宕機(jī)也可能使消息無法被即時(shí)讀取并處理。另外,即使消費(fèi)者即時(shí)讀取消息,內(nèi)存消息隊(duì)列中的所有消息也可能因?yàn)閗ill-9操作發(fā)生丟失。這些可能性都直接導(dǎo)致了消息不可靠。
因此,需要額外的措施為消息提供可靠保障。一種消息可靠性保障方式是,Server投遞消息后并不立即將消息從Server刪除,而是等到消費(fèi)者接收、處理消息并返回Ack包給Server后,Server才刪除該消息。如果消費(fèi)者沒有發(fā)送Ack包,那么Server將重新投遞該消息。這個(gè)過程確保消息被消費(fèi)者處理,保證了消息可靠。另外,假如消費(fèi)者已處理消息并發(fā)送Ack包給Server,但由于網(wǎng)絡(luò)故障等問題導(dǎo)致Ack包丟失時(shí),那么Server同樣會(huì)重新投遞該消息,導(dǎo)致消息被重復(fù)處理。消息的重復(fù)處理通常由業(yè)務(wù)層面的技術(shù)手段來避免,比如在數(shù)據(jù)庫層面添加主鍵約束等。另一種重復(fù)消息處理的避免方式是客戶端對(duì)每條消息維護(hù)ID, 將被處理消息的ID記錄在列表中,同時(shí)檢查新到消息是否在該列表中。
RabbitMQ中的Auto Ack和Manual Ack對(duì)應(yīng)著消息不可靠模式和消息可靠模式. Auto Ack即no ack,指消息投后即刪除,對(duì)應(yīng)消息不可靠傳輸。Manual Ack即手動(dòng)Ack,消費(fèi)者處理完消息后使用Ack包通知Server刪除消息,對(duì)應(yīng)消息可靠傳輸。
Auto Ack是RabbitMQ中最常用的模式,性能較好,但具有以下問題。當(dāng)消息通過套接字write操作投遞后,RabbitMQ Server立即刪除該消息,該模式在遇到網(wǎng)絡(luò)故障時(shí)容易發(fā)生消息丟失。另外,假如消費(fèi)者處理消息的速率過低,可能導(dǎo)致消息在消費(fèi)者recv buffer中大量堆積,從而導(dǎo)致Server端send buffer也堆積大量消息, Server端無法繼續(xù)調(diào)用套接字write操作。這樣,一段時(shí)間之后,Server可能強(qiáng)制關(guān)閉消息傳輸鏈接,導(dǎo)致消息不可傳輸。
盡管Auto Ack存在一定風(fēng)險(xiǎn),目前許多公司仍在應(yīng)用Auto Ack模式。使用Auto Ack模式時(shí),開發(fā)者需要注意消費(fèi)者和生產(chǎn)者的實(shí)例數(shù)量比例,使消息生產(chǎn)者產(chǎn)生消息的速率與消費(fèi)者消費(fèi)消息的速率大致持平。
Manual Ack是RabbitMQ 中更加智能的一種模式。Manual Ack在工作時(shí)會(huì)考慮消息消費(fèi)者的消息接收能力,根據(jù)消費(fèi)者的消息接受能力和當(dāng)前接收到的Ack包自動(dòng)調(diào)節(jié)分發(fā)消息的速率,保證消息分發(fā)可靠、不阻塞。具體來說,客戶端通過PrefetchCount告知Server自身堆積消息的能力。
生產(chǎn)端消息不可靠問題及其解決方案
消息生產(chǎn)端同樣存在消息的可靠性問題。從Client Publisher將消息傳遞給Server和從Server將消息傳遞給Client Consumer的過程是完全對(duì)等的,Server和Client Consumer間傳遞消息的可靠性問題在Client Publisher和Server間同樣存在。
Client Publisher首先將消息寫到套接字,再通過網(wǎng)絡(luò)傳遞給Server的套接字buffer,最終由Server讀取該消息。這一過程的潛在網(wǎng)絡(luò)問題也可能使Server端接收不到消息。
另外,Server端本身也可能導(dǎo)致消息不可靠。Server端需要持久化消息,但出于性能開銷的考慮,Server端并不在每次持久化消息時(shí)都刷盤。具體來說,Server端會(huì)對(duì)文件執(zhí)行write操作,將臟數(shù)據(jù)寫入操作系統(tǒng)的緩存中,而不是立即將數(shù)據(jù)寫入磁盤。一般情況下,Server可能每幾百毫秒執(zhí)行一次fsync操作,通過fsync操作將文件的臟數(shù)據(jù)寫入磁盤。由于Server具有宕機(jī)風(fēng)險(xiǎn),那么每次Server宕機(jī)時(shí),還未被fsync操作處理的數(shù)據(jù)就可能丟失,此過程類似于Redis AOF。
RabbitMQ通過生產(chǎn)者事務(wù)和生產(chǎn)者確認(rèn)兩個(gè)方法解決Server產(chǎn)生的數(shù)據(jù)不可靠問題。
生產(chǎn)者事務(wù)的基本原理是采用select和commit指令包裹publish,在消息生產(chǎn)者publish數(shù)據(jù)之前執(zhí)行select操作,相當(dāng)于begin transaction事務(wù)開始,在執(zhí)行若干個(gè)publish操作后,再執(zhí)行commit操作,相當(dāng)于提交事務(wù)。根據(jù)tcp包的有序性,commit包成功接收意味著commit包之前的包也成功接收。因此,收到從Client Publisher傳遞過來的commit包意味著該commit包之前的所有publish包都已成功接收,即所有消息都成功接收。然而,commit包只有等到Server端的fsync操作執(zhí)行完畢時(shí)才返回,因此生產(chǎn)者事務(wù)的效率較低,通常只在有批量publish操作時(shí)才使用生產(chǎn)者事務(wù)模式。也就是說,客戶端將消息累計(jì)起來批量發(fā)送,以降低fsync操作帶來的性能損失。此外,在進(jìn)程中累計(jì)消息也存在風(fēng)險(xiǎn),累計(jì)的消息可能由于進(jìn)程掛掉而丟失。總的來說,生產(chǎn)者事務(wù)由于性能缺點(diǎn)不被RabbitMQ官方推薦。
另一種Server帶來的數(shù)據(jù)不可靠問題的解決方案是生產(chǎn)者確認(rèn)。生產(chǎn)者確認(rèn)類似于消費(fèi)端的Ack機(jī)制,生產(chǎn)者可能連續(xù)發(fā)送多條消息,Server將這些消息異步地通過fsync操作寫入磁盤再異步地給生產(chǎn)者發(fā)送Ack包,告知生產(chǎn)者消息的接收成功。由于Ack包異步傳輸,不影響生產(chǎn)者端消息的正常發(fā)送。生產(chǎn)者確認(rèn)模式下,Ack包批量發(fā)送,并且都攜帶有序號(hào),以告知生產(chǎn)者該序號(hào)以前的所有消息都已正常落盤。盡管RabbitMQ推薦用戶使用生產(chǎn)者確認(rèn)模式,目前的RabbitMQ版本還未實(shí)現(xiàn)消息的重發(fā)機(jī)制,只實(shí)現(xiàn)了Ack包的批量發(fā)送,以通知Client Publisher哪些消息接收成功。當(dāng)消息丟失時(shí),Client Publisher端已publish的消息在進(jìn)程掛掉時(shí)也可能丟失,而不是重新發(fā)送,因此生產(chǎn)者確認(rèn)的作用也不明顯。當(dāng)然,生產(chǎn)者確認(rèn)起到了降低消息發(fā)布速度的作用,減小了消息丟失的數(shù)量。
生產(chǎn)者確認(rèn)中的消息重發(fā)可以通過以下幾種方法實(shí)現(xiàn)。第一種方式在內(nèi)存中累積還未收到Ack包的消息,收到Ack包后刪除該消息,對(duì)于一段時(shí)間內(nèi)還停留在內(nèi)存中的消息,重發(fā)該消息。這種方式將未Ack消息存入內(nèi)存,一旦消息生產(chǎn)者宕機(jī),這些消息也會(huì)丟失。另一種方式將未收到Ack包消息存入磁盤,當(dāng)收到Ack包后刪除該消息,然而,磁盤存儲(chǔ)依賴于fsync操作,降低了系統(tǒng)處理消息的性能。同時(shí),這還會(huì)提高編程的復(fù)雜度,因?yàn)檫@要求發(fā)布消息時(shí)維護(hù)文件隊(duì)列,還要求一個(gè)異步線程將文件隊(duì)列中的消息發(fā)布到Server,帶來了多線程和鎖問題。還有一種方式將未Ack消息存入Redis,但當(dāng)出現(xiàn)網(wǎng)絡(luò)故障時(shí),Redis也是不可靠的。目前提供的生產(chǎn)者確認(rèn)中的消息重發(fā)方案都還存在問題,具體的方案選擇依賴于實(shí)際場(chǎng)景和個(gè)人取舍。
死信隊(duì)列
生產(chǎn)者確認(rèn)中的消息重發(fā)可以通過以下幾種方法實(shí)現(xiàn)。第一種方式在內(nèi)存中累積還未收到Ack包的消息,收到Ack包后刪除該消息,對(duì)于一段時(shí)間內(nèi)還停留在內(nèi)存中的消息,重發(fā)該消息。這種方式將未Ack消息存入內(nèi)存,一旦消息生產(chǎn)者宕機(jī),這些消息也會(huì)丟失。另一種方式將未收到Ack包消息存入磁盤,當(dāng)收到Ack包后刪除該消息,然而,磁盤存儲(chǔ)依賴于fsync操作,降低了系統(tǒng)處理消息的性能。同時(shí),這還會(huì)提高編程的復(fù)雜度,因?yàn)檫@要求發(fā)布消息時(shí)維護(hù)文件隊(duì)列,還要求一個(gè)異步線程將文件隊(duì)列中的消息發(fā)布到Server,帶來了多線程和鎖問題。還有一種方式將未Ack消息存入Redis,但當(dāng)出現(xiàn)網(wǎng)絡(luò)故障時(shí),Redis也是不可靠的。目前提供的生產(chǎn)者確認(rèn)中的消息重發(fā)方案都還存在問題,具體的方案選擇依賴于實(shí)際場(chǎng)景和個(gè)人取舍。
三、死信隊(duì)列
死信隊(duì)列使用了RabbitMQ中的一種特殊隊(duì)列屬性,即x-message-ttl屬性,表示隊(duì)列中消息的構(gòu)建時(shí)間。假如用戶在聲明隊(duì)列時(shí)定義隊(duì)列的x-message-ttl屬性,此后所有進(jìn)入該隊(duì)列的消息都將持有構(gòu)建時(shí)間,到達(dá)構(gòu)建時(shí)間的消息將被刪除。如果還為隊(duì)列配置了回收站屬性,那么即使構(gòu)建時(shí)間到達(dá),RabbitMQ也不會(huì)立即刪除這些消息,而是將這些過期消息丟入回收站,即死信隊(duì)列。
死信隊(duì)列的工作方式如上圖。Client Publisher將消息投遞給路由器,也就是exchange,再由exchange將消息投遞給隊(duì)列,由隊(duì)列生成該消息的構(gòu)建時(shí)間,到達(dá)構(gòu)建時(shí)間的消息將過期,同時(shí)進(jìn)入死信隊(duì)列。過期消息進(jìn)入死信隊(duì)列的方式和進(jìn)入普通隊(duì)列的方式基本一致,即先投遞給exchange路由器,再由exchange投遞消息。消費(fèi)者消費(fèi)死信隊(duì)列,得到的消息是延后的消息,延遲的時(shí)間長(zhǎng)度即構(gòu)建時(shí)間。目前,死信隊(duì)列存在的問題是,一個(gè)隊(duì)列只能設(shè)置一個(gè)構(gòu)建時(shí)間,消息的過期時(shí)間不夠靈活,不能滿足一些特殊場(chǎng)景的需求,比如動(dòng)態(tài)的重試時(shí)間。
死信隊(duì)列的另一個(gè)使用場(chǎng)景是Retry Later,即在一段時(shí)間后才重新處理此前處理失敗的消息,這時(shí)可能用到雙重死信。具體來說,死信隊(duì)列不僅可以接收過期消息,還可以接收被reject的消息,即消費(fèi)端拒絕處理或處理過程發(fā)生異常的消息,Reject操作具有requeue參數(shù),當(dāng)requeue設(shè)為true時(shí)被reject消息會(huì)重新進(jìn)入消息隊(duì)列并被重新投遞,當(dāng)requeue設(shè)為false時(shí)被reject消息將進(jìn)入死信隊(duì)列。假如死信隊(duì)列持有構(gòu)建時(shí)間,那么到達(dá)構(gòu)建消息的消息將重新投遞給原有隊(duì)列,實(shí)現(xiàn)Retry Later。雙重死信在使用過程中需注意消息處理的死循環(huán)問題,因?yàn)橄⒖赡軣o限循環(huán)地進(jìn)入死信隊(duì)列。
生產(chǎn)環(huán)境下使用RabbitMQ應(yīng)注意的事項(xiàng)
生產(chǎn)環(huán)境下,RabbitMQ通過使用集群模式。集群模式下,只有元信息分布在所有節(jié)點(diǎn)中。元信息指隊(duì)列信息,路由器信息等,隊(duì)列中的信息只存儲(chǔ)在一個(gè)節(jié)點(diǎn)中,因此,單個(gè)節(jié)點(diǎn)宕機(jī)會(huì)導(dǎo)致所有節(jié)點(diǎn)都不可用。另外,RabbitMQ的所有節(jié)點(diǎn)間存在轉(zhuǎn)發(fā)機(jī)制,即允許節(jié)點(diǎn)轉(zhuǎn)發(fā)其他目標(biāo)節(jié)點(diǎn)的消息處理請(qǐng)求,這樣客戶端只需連接到任意一個(gè)節(jié)點(diǎn)就可以實(shí)現(xiàn)其消息轉(zhuǎn)發(fā)需求。
隊(duì)列的高可用依賴于RabbitMQ的鏡像隊(duì)列,即在其他節(jié)點(diǎn)上備份某節(jié)點(diǎn)的消息內(nèi)容。這樣,當(dāng)消息所在主節(jié)點(diǎn)宕機(jī)時(shí),其他鏡像節(jié)點(diǎn)可以替代主節(jié)點(diǎn)完成消息傳遞任務(wù)。
通常情況下,鏡像節(jié)點(diǎn)是默默無聞的,客戶端無需感知鏡像節(jié)點(diǎn)的存在。只有當(dāng)主節(jié)點(diǎn)宕機(jī)時(shí),鏡像節(jié)點(diǎn)才發(fā)揮作用。鏡像隊(duì)列的配置如下:
- Ha-mode具有三個(gè)選項(xiàng),all指將所有隊(duì)列的信息存入所有節(jié)點(diǎn),這種模式最安全,但也最浪費(fèi)存儲(chǔ)空間;exactly指由用戶精確指定每個(gè)隊(duì)列的復(fù)制數(shù),當(dāng)ha-mode設(shè)置為exactly,ha-params設(shè)置為2時(shí)表示“一主一從”,這種模式是官方推薦的;nodes指由用戶指定副本所在的節(jié)點(diǎn),這種模式極少被使用。
- x-queue-master-locator用于設(shè)置存儲(chǔ)隊(duì)列主節(jié)點(diǎn)的RabbitMQ節(jié)點(diǎn)。min-master指將隊(duì)列主節(jié)點(diǎn)設(shè)置在隊(duì)列數(shù)量最少的RabbitMQ節(jié)點(diǎn),client-local指將隊(duì)列主節(jié)點(diǎn)設(shè)置在當(dāng)前客戶端所在的RabbitMQ節(jié)點(diǎn),random即隨機(jī)選擇節(jié)點(diǎn)。
- Ha-sync-mode用于鏡像節(jié)點(diǎn)代替宕機(jī)主節(jié)點(diǎn)并創(chuàng)建新節(jié)點(diǎn)以彌補(bǔ)缺失節(jié)點(diǎn)時(shí),設(shè)置新節(jié)點(diǎn)上數(shù)據(jù)的同步策略。automatic指自動(dòng)地將新主節(jié)點(diǎn)上數(shù)據(jù)全部同步給新節(jié)點(diǎn),manual指不同步新主節(jié)點(diǎn)上的老數(shù)據(jù),只同步新產(chǎn)生的數(shù)據(jù)。由于節(jié)點(diǎn)間數(shù)據(jù)同步需要耗費(fèi)時(shí)間,長(zhǎng)時(shí)間的數(shù)據(jù)同步可能會(huì)影響服務(wù)的穩(wěn)定性,但通常情況下RabbitMQ的節(jié)點(diǎn)堆積的數(shù)據(jù)量并不大,因此RabbitMQ官方推薦使用Automatic進(jìn)行數(shù)據(jù)同步。
- Ha-sync-batch-size指節(jié)點(diǎn)間批量同步的數(shù)據(jù)量。
- Ha-promote-on-shutdown表示主動(dòng)停止主節(jié)點(diǎn)的服務(wù)時(shí),其他節(jié)點(diǎn)如何替代主節(jié)點(diǎn)。Always指其他節(jié)點(diǎn)總是能順利地替代主節(jié)點(diǎn),when-synced要求與原主節(jié)點(diǎn)數(shù)據(jù)完全一致的節(jié)點(diǎn)才能替代主節(jié)點(diǎn)。
- Ha-promote-on-failure表示異常情況下其他節(jié)點(diǎn)如何替代主節(jié)點(diǎn),always和when-synced的含義與Ha-promote-on-shutdown中一致。
許多公司為RabbitMQ集群設(shè)置了內(nèi)存模式,認(rèn)為內(nèi)存模式無需落盤,能夠提升系統(tǒng)性能。但實(shí)際上,RabbitMQ官方文檔指出,內(nèi)存模式無法提升系統(tǒng)性能,它只提升了產(chǎn)生元信息數(shù)據(jù)的速度,即Ram Node指將元信息存入內(nèi)存,可以提升元信息的創(chuàng)建速度,而不是消息數(shù)據(jù)的性能。這是使用RabbitMQ時(shí)的一個(gè)常見誤區(qū)。
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的RabbitMQ 的延时队列和镜像队列原理与实战的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 目标检测算法图解:一文看懂RCNN系列算
- 下一篇: 从濒临解散到浴火重生,OceanBase