一 分析
數(shù)據(jù)的丟失問題镰绎,可能出現(xiàn)在生產者狗唉、MQ摊册、消費者中罢缸,咱們從 RabbitMQ 和 Kafka 分別來分析一下吧校读。
二 RabbitMQ
2.1 RabbitMQ生產者弄丟了數(shù)據(jù)
生產者將數(shù)據(jù)發(fā)送到 RabbitMQ 的時候,可能數(shù)據(jù)就在半路給搞丟了祖能,因為網絡問題啥的歉秫,都有可能。RabbitMQ針對生產者丟失數(shù)據(jù)可以有兩種方案解決問題:
-
Plan-A : 此時可以選擇用 RabbitMQ 提供的事務功能养铸,就是生產者發(fā)送數(shù)據(jù)之前開啟 RabbitMQ 事務
channel.txSelect
雁芙,然后發(fā)送消息,如果消息沒有成功被 RabbitMQ 接收到钞螟,那么生產者會收到異常報錯兔甘,此時就可以回滾事務channel.txRollback
,然后重試發(fā)送消息鳞滨;如果收到了消息洞焙,那么可以提交事務channel.txCommit
。
// 開啟事務
channel.txSelect
try {
// 這里發(fā)送消息
} catch (Exception e) {
channel.txRollback
// 這里再次重發(fā)這條消息
}
// 提交事務
channel.txCommit
- Plan-A的缺陷: :RabbitMQ 事務機制(同步)一搞拯啦,基本上吞吐量會下來澡匪,因為太耗性能。
-
Plan-B : 要確保說寫 RabbitMQ 的消息別丟褒链,可以開啟
confirm
模式唁情,在生產者那里設置開啟confirm
模式之后,你每次寫的消息都會分配一個唯一的 id甫匹,然后如果寫入了 RabbitMQ 中甸鸟,RabbitMQ 會給你回傳一個ack
消息,告訴你說這個消息 ok 了兵迅。如果 RabbitMQ 沒能處理這個消息抢韭,會回調你的一個nack
接口,告訴你這個消息接收失敗恍箭,你可以重試刻恭。而且你可以結合這個機制自己在內存里維護每個消息 id 的狀態(tài),如果超過一定時間還沒接收到這個消息的回調季惯,那么你可以重發(fā)吠各。
RabbitMQ 事務機制與confirm
機制
事務機制和
confirm
機制最大的不同在于,事務機制是同步的勉抓,你提交一個事務之后會阻塞在那兒贾漏,但是confirm
機制是異步的,你發(fā)送個消息之后就可以發(fā)送下一個消息藕筋,然后那個消息 RabbitMQ 接收了之后會異步回調你的一個接口通知你這個消息接收到了纵散。所以一般在生產者這塊避免數(shù)據(jù)丟失,都是用
confirm
機制的。
2.2 RabbitMQ 弄丟了數(shù)據(jù)
RabbitMQ 自己弄丟了數(shù)據(jù)伍掀,這個你必須開啟 RabbitMQ 的持久化掰茶,就是消息寫入之后會持久化到磁盤,哪怕是 RabbitMQ 自己掛了蜜笤,恢復之后會自動讀取之前存儲的數(shù)據(jù)濒蒋,一般數(shù)據(jù)不會丟。除非極其罕見的是把兔,RabbitMQ 還沒持久化沪伙,自己就掛了,可能導致少量數(shù)據(jù)丟失县好,但是這個概率較小围橡。
設置持久化有兩個步驟:
- 創(chuàng)建 queue 的時候將其設置為持久化
這樣就可以保證 RabbitMQ 持久化 queue 的元數(shù)據(jù),但是它是不會持久化 queue 里的數(shù)據(jù)的缕贡。 - 第二個是發(fā)送消息的時候將消息的
deliveryMode
設置為 2
就是將消息設置為持久化的翁授,此時 RabbitMQ 就會將消息持久化到磁盤上去。
必須要同時設置這兩個持久化才行晾咪,RabbitMQ 哪怕是掛了收擦,再次重啟,也會從磁盤上重啟恢復 queue禀酱,恢復這個 queue 里的數(shù)據(jù)炬守。
注意,哪怕是你給 RabbitMQ 開啟了持久化機制剂跟,也有一種可能,就是這個消息寫到了 RabbitMQ 中酣藻,但是還沒來得及持久化到磁盤上曹洽,結果不巧,此時 RabbitMQ 掛了辽剧,就會導致內存里的一點點數(shù)據(jù)丟失送淆。
所以,持久化可以跟生產者那邊的 confirm
機制配合起來怕轿,只有消息被持久化到磁盤之后偷崩,才會通知生產者 ack
了,所以哪怕是在持久化到磁盤之前撞羽,RabbitMQ 掛了阐斜,數(shù)據(jù)丟了,生產者收不到 ack
诀紊,你也是可以自己重發(fā)的谒出。
2.3 RabbitMQ消費端弄丟了數(shù)據(jù)
消費者丟失了數(shù)據(jù),主要是因為消費的時候,剛消費到笤喳,還沒處理为居,結果進程掛了,比如重啟了杀狡,那么就尷尬了蒙畴,RabbitMQ 認為你都消費了,這數(shù)據(jù)就丟了呜象。
這個時候得用 RabbitMQ 提供的 ack
機制忍抽,簡單來說,就是你必須關閉 RabbitMQ 的自動 ack
董朝,可以通過一個 api 來調用就行鸠项,然后每次你自己代碼里確保處理完的時候,再在程序里 ack
一把子姜。這樣的話祟绊,如果你還沒處理完,不就沒有 ack
了哥捕?那 RabbitMQ 就認為你還沒處理完牧抽,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理,消息是不會丟的遥赚。
2.4 總結
RabbitMQ 消息丟失及對應解決方案:
- 生產者丟失:
- 方案1:開啟RabbitMQ事務機制(同步扬舒,影響性能,不推薦)凫佛。
- 方案2:開啟RabbitMQ confirm機制(異步讲坎,推薦)
- RabbitMQ本身丟失:
- 開啟RabbitMQ持久化機制。
- 配合生產者那邊的confirm機制保證小時持久化后才進行ack.
- 消費者丟失:
- 關閉RabbitMQ自動ACK機制愧薛,在程序顯示調用API進行確認晨炕。
三 Kafka
3.1 Kafka消費端弄丟了數(shù)據(jù)
- 唯一可能導致消費者弄丟數(shù)據(jù)的情況,就是說毫炉,你消費到了這個消息瓮栗,然后消費者那邊自動提交了 offset,讓 Kafka 以為你已經消費好了這個消息瞄勾,但其實你才剛準備處理這個消息费奸,你還沒處理,你自己就掛了进陡,此時這條消息就丟咯愿阐。
- Kafka 會自動提交 offset,那么只要關閉自動提交 offset四濒,在處理完之后自己手動提交 offset换况,就可以保證數(shù)據(jù)不會丟职辨。但是此時確實還是可能會有重復消費,比如你剛處理完戈二,還沒提交 offset舒裤,結果自己掛了,此時肯定會重復消費一次觉吭,自己保證冪等性就好了腾供。
3.2 Kafka 弄丟了數(shù)據(jù)
這塊比較常見的一個場景,就是 Kafka 某個 broker 宕機鲜滩,然后重新選舉 partition 的 leader伴鳖。大家想想,要是此時其他的 follower 剛好還有些數(shù)據(jù)沒有同步徙硅,結果此時 leader 掛了榜聂,然后選舉某個 follower 成 leader 之后,不就少了一些數(shù)據(jù)嗓蘑?這就丟了一些數(shù)據(jù)啊须肆。
所以此時一般是要求起碼設置如下 4 個參數(shù):
- 給 topic 設置
replication.factor
參數(shù):這個值必須大于 1,要求每個 partition 必須有至少 2 個副本桩皿。 - 在 Kafka 服務端設置
min.insync.replicas
參數(shù):這個值必須大于 1豌汇,這個是要求一個 leader 至少感知到有至少一個 follower 還跟自己保持聯(lián)系,沒掉隊泄隔,這樣才能確保 leader 掛了還有一個 follower 吧拒贱。 - 在 producer 端設置
acks=all
:這個是要求每條數(shù)據(jù),必須是寫入所有 replica 之后佛嬉,才能認為是寫成功了逻澳。 - 在 producer 端設置
retries=MAX
(很大很大很大的一個值,無限次重試的意思):這個是要求一旦寫入失敗巷燥,就無限重試赡盘,卡在這里了。
我們生產環(huán)境就是按照上述要求配置的缰揪,這樣配置之后,至少在 Kafka broker 端就可以保證在 leader 所在 broker 發(fā)生故障葱淳,進行 leader 切換時钝腺,數(shù)據(jù)不會丟失。
3.3 生產者會不會弄丟數(shù)據(jù)
如果按照上述的思路設置了 acks=all
赞厕,一定不會丟艳狐,要求是,你的 leader 接收到消息皿桑,所有的 follower 都同步到了消息之后毫目,才認為本次寫成功了蔬啡。如果沒滿足這個條件,生產者會自動不斷的重試镀虐,重試無限次箱蟆。