消息隊列會丟失消息嗎?
答案是肯定的叛溢,所以對于業(yè)務(wù)嚴(yán)謹(jǐn)?shù)臄?shù)據(jù)塑悼,我們要確保其在消息隊列中的安全,不能丟楷掉。
要想解決不丟的問題厢蒜,首先要弄清楚 消息是怎么丟的呢?
丟消息的關(guān)鍵點有3個:
- Producer 發(fā)送消息的過程
- 消息隊列的消息存儲
- Consumer 消費消息的過程
下面挨個看看都是怎么丟的烹植,以及解決方案斑鸦。
會以 RabbitMQ 和 Kafka 這兩個常用的消息系統(tǒng)來說明。
1. Producer 弄丟消息
Producer 向 MQ 發(fā)消息草雕,很簡單巷屿,發(fā)過去就完事兒了。
但是墩虹,在發(fā)送圖中是存在危險的嘱巾,例如網(wǎng)絡(luò)問題等等,導(dǎo)致 MQ 沒有正常收到诫钓。
怎么解決呢旬昭? 思路很簡單,讓 MQ 發(fā)一個 接受確認(rèn)聲明(ack) 就行了尖坤,就像快遞需要簽收一樣稳懒。
例如 RabbitMQ闲擦,有兩種方式可以確保發(fā)送消息的安全慢味。
1)事務(wù)消息
Producer 發(fā)送消息之前,先開啟事務(wù)墅冷,然后再發(fā)送纯路。
如果 RabbitMQ 沒有正常收到消息,Producer 會收到異常信息寞忿,回滾事務(wù)驰唬。
如果正常接收了,Producer 就提交事務(wù)。
很可靠叫编,但效率低辖佣,因為這個事務(wù)模式是同步的,會產(chǎn)生阻塞搓逾。
2)confirm 確認(rèn)模式
Producer 開啟 confirm 模式卷谈,發(fā)送消息的時候,RabbitMQ 會給這個消息分配一個唯一的 ID霞篡。
成功寫入隊列之后世蔗,RabbitMQ 會向 Producer 發(fā)送一個 ack 消息,說明此 ID 的消息已經(jīng)成功發(fā)送朗兵。
confirm 模式還有一個回調(diào)機(jī)制污淋,Producer 可以準(zhǔn)備一個失敗的接口,供 RabbitMQ 在接收失敗時調(diào)用余掖。
Producer 收到失敗通知寸爆,或者超時了,可以執(zhí)行相應(yīng)的處理邏輯盐欺,例如重發(fā)而昨。
confirm 模式是異步的,比事務(wù)消息更高效找田,使用更為廣泛歌憨。
Kafka 也是使用的 ack 方式,使用方式很簡單墩衙,只要配置:
ack=all
確保 Kafka 在完全接收成功后才發(fā)送確認(rèn)通知务嫡,這樣就一定不會發(fā)丟了。
2. MQ 在存儲期間弄丟消息
MQ 成功接收消息之后漆改,需要保存起來心铃,等著 Consumer 消費。
在這個保存期間挫剑,也可以能丟失消息去扣。
這通常是由 MQ 故障引起的。
RabbitMQ 想要保障消息不丟樊破,需要開啟持久化愉棱,消息就會寫入磁盤。
即使 RabbitMQ 宕機(jī)了哲戚,只要磁盤沒事兒奔滑,重啟之后還可以重新把消息加載進(jìn)來。
如果想進(jìn)一步的保障消息安全顺少,就需要配置 RabbitMQ 的鏡像集群了朋其,來確保高可用王浴。
Kafka 是天然的分布式系統(tǒng),Topic 分為多個 Partition梅猿,每個 Partition 又有多個副本氓辣。
Partition 的多個副本,分為 Leader 和 Follower袱蚓。
Leader 負(fù)責(zé)處理消息的讀寫筛婉,F(xiàn)ollower 負(fù)責(zé)備份。
前面說的 Kafka 配置 ack=all
癞松,就是告訴Kafka爽撒,Leader 和所有 Follower 全都接收到了,才算發(fā)送 ack 確認(rèn)响蓉,只有 Leader 自己接收成功是不算的硕勿。
否則的話,如果 Leader 接收完成就告訴 Producer OK 了枫甲,在 Leader 同步給 Follower 之前源武,Leader 宕機(jī)了,Kafka 會從 Follower 中選舉出新的 Leader想幻。那么粱栖,老 Leader 在臨終前沒有同步的消息就丟失了。
為了保障消息的安全脏毯,這 4 個參數(shù)要設(shè)置好:
replication.factor
用于指定 Partition 副本的數(shù)量闹究,必須大于 1,就是至少要有 2 個副本食店,一個 Leader 一個 Follower渣淤。
min.insync.replicas
用于指定幾個副本成功寫入才提交消息,只有提交之后的消息才能被 Consumer 消費吉嫩。
此值至少大于 1价认,這樣就保障 Leader 之外至少有一個副本同步到了這條消息,不怕 Leader 宕掉了自娩。
acks=all
用于指定幾個副本接收到消息之后向 Producer 發(fā)送 ack用踩。例如值為 1,表示 Leader 收到就可以了忙迁,“all” 表示 “所有副本”脐彩,也可以寫 “-1”,等同于 “all”动漾。
retries=999
用于指定 Producer 發(fā)送失敗后的重試次數(shù)丁屎,可以設(shè)為一個很大的數(shù)荠锭,表示失敗了就重試旱眯,提升發(fā)送成功幾率。
3. Consumer 弄丟消息
例如 Consumer 成功接收到了消息 “123”,MQ 就會移除這條消息删豺。
但在 Consumer 處理完這條消息之前共虑,宕機(jī)了。
Consumer 重啟之后繼續(xù)從 MQ 拿消息呀页,這次拿到的就是下一條消息 “124”妈拌,那么 “123” 就丟了。
所以蓬蝶,Consumer 只是接收到消息是不夠的尘分,成功處理完成才行。
這就和 MQ 的消費確認(rèn)機(jī)制有關(guān)了丸氛。
RabbitMQ 默認(rèn)是 Consumer 成功接收消息之后就發(fā)送 ack 確認(rèn)培愁,RabbitMQ 就認(rèn)為消費成功了。
關(guān)閉自動的 Consumer ack 就行缓窜,改為手動發(fā)送確認(rèn)通知定续。
Kafka 的 Consumer 發(fā)送的不是 ack 確認(rèn),而是 offset禾锤,告訴 Kafka 已經(jīng)消費到哪個位置了私股。
默認(rèn)是 Consumer 接收后自動提交 offset,所以也需要關(guān)閉恩掷,改為手動提交倡鲸。
小結(jié)一下,要想消息不丟黄娘,需要發(fā)消息的時候確認(rèn)發(fā)送成功了旦签,MQ 存儲的時候要是高可靠的,Consumer 消費的時候寸宏,不能接收之后就確認(rèn)宁炫,真正處理完成才行。
推薦閱讀