一文讀懂rabbitMQ和rocketMQ的消息可靠性機制
一文讀懂rabbitMQ和rocketMQ的消息可靠性機制
在我們大多數(shù)場景中,MQ消息都要保證可靠性,消息可靠性應(yīng)該是我們最關(guān)心的一個細節(jié),沒有之一;而各個MQ實現(xiàn)的可靠性保證都不同,同時實現(xiàn)機制也不同,只有知道各個MQ實現(xiàn)是如何保證消息可靠性的,才能在使用的過程中不丟消息;
rabbitMQ
對于rabbitMQ,消息可靠性是從以下幾點來保證的:
消息持久化;
發(fā)布者確認;
消費者確認;
消息持久化
對于rabbitMQ,默認情況下消息是不持久化的,這是為了性能考慮,但是這樣會導(dǎo)致消息在服務(wù)器重啟后丟失,這在我們大多數(shù)場景下都是不可接受的,所以,我們
要開啟消息持久化,而rabbitMQ的消息持久化分為兩種:
同步持久化,也就是rabbitMQ broker(服務(wù)端)每接收到一個消息都會立即持久化到磁盤上,顯而易見的,這種持久化方式性能很差;
異步持久化,也就是rabbitMQ broker(服務(wù)端)接收到消息后不會立即持久化到磁盤上,而是會按照時間段批量將內(nèi)存中的數(shù)據(jù)刷新到磁盤,這種方式的性能較
好,但是也存在一個問題,那就是如果服務(wù)在刷盤前重啟了會導(dǎo)致內(nèi)存中部分數(shù)據(jù)丟失;
對于這兩種方式,我們優(yōu)先選擇使用異步持久化,因為同步持久化的性能太差(官方測試數(shù)據(jù)同步持久化相較于異步持久化性能差了200倍左右),那我們怎么防止刷盤前服務(wù)重啟導(dǎo)致內(nèi)存中的短時間內(nèi)的數(shù)據(jù)丟失呢?rabbitMQ給出了發(fā)布者確認的發(fā)布模式來解決該問題;
發(fā)布者確認
對于rabbitMQ,發(fā)布者確認需要手動開啟,默認是沒有開啟的,也就是說默認情況下我們消息發(fā)出去就完事兒了,但是服務(wù)端有沒有成功處理并不一定,此時雖然消息
發(fā)布成功但是broker可能并沒有正確處理該消息導(dǎo)致消息丟失,所以為了消息的可靠性,我們需要開啟發(fā)布確認;開啟了發(fā)布確認后,在我們發(fā)布完消息后服務(wù)端會在
接收到消息并成功處理后返回給我們一個ack,表示消息接收處理完畢(注意,只是broker端處理完畢,不是消費者消費完成),而broker的ack時機對于我們很關(guān)
鍵,broker會在以下時機給我們返回ack:
如果未開啟消息持久化,則消息在被路由到各個隊列(如果是鏡像隊列,這意味著所有鏡像隊列都成功接收到消息)后broker返回ack;
如果開啟消息持久化,則消息在被路由到各個隊列并且所有需要持久化的隊列持久化完成后返回ack;而如果其中某部分隊列持久化未完成則broker會返回nack,表
示服務(wù)端接收失敗,需要生產(chǎn)者重新發(fā)布消息;注意:此時雖然部分隊列持久化失敗,但是持久化成功的那部分是不會回滾的,也就是說對于持久化成功的隊列上綁定
的消費者可能會重復(fù)收到消息,此時消費者應(yīng)該去重;
可以看出,發(fā)布者確認可以解決消息異步持久化中的問題,這兩項措施保證了我們的消息肯定會發(fā)布成功并且不會丟失,后續(xù)就是消費者的事情了;
注意:其實以上措施只能保證在硬件正常的情況下消息不會丟失,而如果broker是單點部署的,則這個broker的磁盤損壞仍然會導(dǎo)致數(shù)據(jù)丟失,而如果broker是
集群部署的,如果集群中所有broker的磁盤都損壞,此時消息也會丟失,由于硬件故障是無法避免的,只能根據(jù)消息的重要性做集群,集群規(guī)模越大、磁盤可靠性越
高,消息丟失的概率越??;
消費者確認
通過上述的消息持久化和發(fā)布者確認,我們已經(jīng)可以保證消息在發(fā)布后不會丟失了,但是消息仍然可能會在消費的過程中丟失,這與rabbitMQ的消費確認模型有關(guān)系,
rabbitMQ的消費確認模型有兩種:
自動確認
手動確認
如果是手動確認,只要代碼沒問題,一般不會導(dǎo)致消息丟失,導(dǎo)致消息丟失的是消費者使用自動確認模型時的場景;消費者使用自動確認模型后,當消費者拉取到(接收
到)消息后會自動確認消息已經(jīng)消費,消息被消費者確認后就會從指定的隊列中刪除,但是此時消費者并沒有成功處理該消息,如果此時消費者重啟或者處理失敗,因為
隊列中的消息已經(jīng)刪除不會重新投遞,這就意味著消息丟失了;
所以, 不要輕易使用自動確認模型 ,除非是消息無關(guān)緊要,例如日志消息,丟了影響也不大,或者即使消息丟了,也只是會導(dǎo)致數(shù)據(jù)暫時不一致,系統(tǒng)可以通過
其他補償措施自動到達最終一致性;
rocketMQ
消息持久化
對于rocketMQ來說,正常情況下所有消息都肯定會持久化,也就是正常情況下我們不用開啟任何選項rocketMQ就會把我們的消息持久化,重啟rocketMQ后消息不會丟失;
此時我們只需要考慮異常情況下的處理即可;經(jīng)典異常場景有如下幾種:
Broker非正常關(guān)閉
Broker異常Crash
OS Crash
機器掉電,但是能立即恢復(fù)供電情況
機器無法開機(可能是cpu、主板、內(nèi)存等關(guān)鍵設(shè)備損壞)
磁盤設(shè)備損壞
前四種情況屬于硬件資源可立即恢復(fù)的, rocketMQ此時根據(jù)刷盤方式不會丟失數(shù)據(jù)或者丟失少部分數(shù)據(jù)(如果是異步刷盤則內(nèi)存中的數(shù)據(jù)會丟失,如果同步刷盤就不會有這種情況,但是性能勢必會降低);
而后兩種則屬于單點問題,即機器故障無法恢復(fù),如果想要避免這種情況可以使用同步雙寫,這樣可以避免單點故障,但是性能會降低;(rocketMQ從3.0開始支持同步雙寫);
發(fā)布者確認
在rocketMQ中,通信分為三種情況:
同步通信;
異步通信;
單向(oneway)
其中同步通信和異步通信都是有ACK的,而oneway則是發(fā)出消息后就不管broker有沒有收到了,也不會有ACK,基本用于心跳,而我們需要關(guān)注的就是同步/異步通信,因為同步通信或者異步通信僅僅只是IO模型上的區(qū)別,并不會影響消息的可靠性,導(dǎo)致消息丟失,
所以我們不關(guān)注通信模型,我們只關(guān)注ACK的時機,即broker什么時候會ACK,這個是真正影響消息可靠性的因素;
rocketMQ中數(shù)據(jù)寫盤也是分為兩種,即同步寫盤和異步寫盤,同步寫盤效率低,異步寫盤效率高;
如果broker選擇了同步寫盤,則消息會在被真正寫入磁盤后進行ACK,此時對于生產(chǎn)者來說只要接到ACK基本上消息不會丟了(為什么是基本上?請參考消息持久化部分);
如果broker選擇了異步寫盤,則消息會在被寫入page cache之后進行ACK,此時依賴OS的刷盤機制,如果在OS刷盤前OS崩潰、機器掉電等,短時間內(nèi)的消息就會因為來不及寫入磁盤而丟失;
所以,對于一定不能丟失的數(shù)據(jù),我們可以犧牲性能來選擇使用同步刷盤模型,而對于沒有那么重要或者有補償機制的消息,我們就可以使用更高效的異步刷盤模型;
消費者確認
對于rocketMQ,消費者確認并不重要(對于消息可靠性來說),這與rocketMQ的數(shù)據(jù)存儲結(jié)構(gòu)有關(guān),rocketMQ的broker對于消息的存儲可以認為是構(gòu)建了一個數(shù)據(jù)庫,保存的有消息、消費隊列、索引,rocketMQ并不會在ACK之后立即把消息刪除(具體什么時候刪除取決于你的磁盤大小,如果磁盤足夠大,rocketMQ可以一直不刪除消息),消費者仍然有途徑可以重復(fù)消費該消息;
對比
在可靠性這塊兒,rabbitMQ和rocketMQ底層其實大同小異,不同的是rocketMQ默認就是會把消息落盤的,幾乎不會刪除歷史消息(rocketMQ在磁盤不夠的時候才會刪除歷史消息,而rabbitMQ則會在消費者確認消費后就刪除),正因為rocketMQ的這一系列策略,即使是一個對MQ沒有任何了解的小白用戶,直接拿來用可靠性就要高出rabbitMQ很多(當然真正理解兩個MQ的底層可靠性保障機制后其實是差不多的),所以rocketMQ才會聲稱自己是金融級、高可靠的MQ中間件;
