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