消息丟失怎麼辦?分析RabbitMQ 和 Kafka 消息丟失的原因和解決辦法

如果說你這個是用 MQ 來傳遞非常核心的消息,比如說計費、扣費的一些消息,那必須確保這個 MQ 傳遞過程中絕對不會把計費消息給弄丟

首先要明確:數據的丟失問題,可能出現在生產者、MQ、消費者中,咱們從 RabbitMQ 和 Kafka 分別來分析一下吧。

1、RabbitMQ

1.1 生產者弄丟了數據

生產者將數據發送到 RabbitMQ 的時候,可能數據就在半路給搞丟了,因爲網絡問題啥的,都有可能。

此時可以選擇用 RabbitMQ 提供的事務功能,就是生產者發送數據之前開啓 RabbitMQ 事務channel.txSelect,然後發送消息,如果消息沒有成功被 RabbitMQ 接收到,那麼生產者會收到異常報錯,此時就可以回滾事務channel.txRollback,然後重試發送消息;如果收到了消息,那麼可以提交事務channel.txCommit

// 開啓事務
channel.txSelect
try {
    // 這裏發送消息
} catch (Exception e) {
    channel.txRollback

    // 這裏再次重發這條消息
}

// 提交事務
channel.txCommit

但是問題是,RabbitMQ 事務機制(同步)一搞,基本上吞吐量會下來,因爲太耗性能

所以一般來說,如果你要確保說寫 RabbitMQ 的消息別丟,可以開啓 confirm 模式。

在生產者那裏設置開啓 confirm 模式之後,你每次寫的消息都會分配一個唯一的 id,然後如果寫入了 RabbitMQ 中,RabbitMQ 會給你回傳一個 ack 消息,告訴你說這個消息 ok 了。如果 RabbitMQ 沒能處理這個消息,會回調你的一個 nack 接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存裏維護每個消息 id 的狀態,如果超過一定時間還沒接收到這個消息的回調,那麼你可以重發。

事務機制和 confirm 機制最大的不同在於:

  • 事務機制是同步的,你提交一個事務之後會阻塞在那兒;
  • confirm 機制是異步的,你發送個消息之後就可以發送下一個消息,然後那個消息 RabbitMQ 接收了之後會異步回調你的一個接口通知你這個消息接收到了。

所以一般在生產者這塊避免數據丟失,都是用 confirm 機制的。

1.2 RabbitMQ 弄丟了數據

如果是 RabbitMQ 自己弄丟了數據,這個你必須開啓 RabbitMQ 的持久化,就是消息寫入之後會持久化到磁盤,哪怕是 RabbitMQ 自己掛了,恢復之後會自動讀取之前存儲的數據,一般數據不會丟。除非極其罕見的是,RabbitMQ 還沒持久化,自己就掛了,可能導致少量數據丟失,但是這個概率較小。

設置持久化有兩個步驟

  • 創建 queue 的時候將其設置爲持久化;

    這樣就可以保證 RabbitMQ 持久化 queue 的元數據,但是它是不會持久化 queue 裏的數據的。
  • 第二個是發送消息的時候將消息的 deliveryMode 設置爲 2;

    就是將消息設置爲持久化的,此時 RabbitMQ 就會將消息持久化到磁盤上去。

必須要同時設置這兩個持久化纔行,RabbitMQ 哪怕是掛了,再次重啓,也會從磁盤上重啓恢復 queue,恢復這個 queue 裏的數據。

注意:

哪怕是你給 RabbitMQ 開啓了持久化機制,也有一種可能 -- 就是這個消息寫到了 RabbitMQ 中,但是還沒來得及持久化到磁盤上,結果不巧,此時 RabbitMQ 掛了,就會導致內存裏的一點點數據丟失。

所以,持久化可以跟生產者那邊的 confirm 機制配合起來,只有消息被持久化到磁盤之後,纔會通知生產者 ack 了,所以哪怕是在持久化到磁盤之前,RabbitMQ 掛了,數據丟了,生產者收不到 ack,你也是可以自己重發的。

1.3 消費端弄丟了數據

RabbitMQ 如果丟失了數據,主要是因爲你消費的時候,剛消費到,還沒處理,結果進程掛了。比如重啓了,那麼就尷尬了,RabbitMQ 認爲你都消費了,這數據就丟了。

這個時候得用 RabbitMQ 提供的 ack 機制,簡單來說,就是你必須關閉 RabbitMQ 的自動 ack,可以通過一個 api 來調用就行,然後每次你自己代碼裏確保處理完的時候,再在程序裏 ack 一把。這樣的話,如果你還沒處理完,不就沒有 ack 了?那 RabbitMQ 就認爲你還沒處理完,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理,消息是不會丟的。

2、Kafka

2.1 消費端弄丟了數據

唯一可能導致消費者弄丟數據的情況,就是你消費到了這個消息,然後消費者那邊自動提交了 offset,讓 Kafka 以爲你已經消費好了這個消息,但其實你纔剛準備處理這個消息,你還沒處理,你自己就掛了,此時這條消息就丟咯。

這不是跟 RabbitMQ 差不多嗎,大家都知道 Kafka 會自動提交 offset,那麼只要關閉自動提交 offset,在處理完之後自己手動提交 offset,就可以保證數據不會丟。但是此時確實還是可能會有重複消費,比如你剛處理完,還沒提交 offset,結果自己掛了,此時肯定會重複消費一次,自己保證冪等性就好了。

生產環境碰到的一個問題,就是說我們的 Kafka 消費者消費到了數據之後是寫到一個內存的 queue 裏先緩衝一下,結果有的時候,你剛把消息寫入內存 queue,然後消費者會自動提交 offset。然後此時我們重啓了系統,就會導致內存 queue 裏還沒來得及處理的數據就丟失了。

2.2 Kafka 弄丟了數據

這塊比較常見的一個場景,就是 Kafka 某個 broker 宕機,然後重新選舉 partition 的 leader。大家想想,要是此時其他的 follower 剛好還有些數據沒有同步,結果此時 leader 掛了,然後選舉某個 follower 成 leader 之後,不就少了一些數據?這就丟了一些數據啊。

生產環境也遇到過,我們也是,之前 Kafka 的 leader 機器宕機了,將 follower 切換爲 leader 之後,就會發現說這個數據就丟了。

所以此時一般是要求起碼設置如下 4 個參數:

  • 給 topic 設置 replication.factor 參數:這個值必須大於 1,要求每個 partition 必須有至少 2 個副本。
  • 在 Kafka 服務端設置 min.insync.replicas 參數:這個值必須大於 1,這個是要求一個 leader 至少感知到有至少一個 follower 還跟自己保持聯繫,沒掉隊,這樣才能確保 leader 掛了還有一個 follower 吧。
  • 在 producer 端設置 acks=all:這個是要求每條數據,必須是寫入所有 replica 之後,才能認爲是寫成功了
  • 在 producer 端設置 retries=MAX(很大很大很大的一個值,無限次重試的意思):這個是要求一旦寫入失敗,就無限重試,卡在這裏了。

我們生產環境就是按照上述要求配置的,這樣配置之後,至少在 Kafka broker 端就可以保證在 leader 所在 broker 發生故障,進行 leader 切換時,數據不會丟失。

2.3 生產者會不會弄丟數據?

如果按照上述的思路設置了 acks=all,一定不會丟;

要求是:你的 leader 接收到消息,所有的 follower 都同步到了消息之後,才認爲本次寫成功了。

如果沒滿足這個條件,生產者會自動不斷的重試,重試無限次。

 

《如何解決消息隊列幾百萬消息持續積壓幾小時的問題?》https://blog.csdn.net/weixin_44259720/article/details/104845731


尊重原創,本文原創地址:https://jsbintask.cn/2019/01/28/interview/interview-middleware-reliable/

 

少俠請留步 ... ヾ(◍°∇°◍)ノ゙ ... 
歡迎點贊、評論、加關注,讓更多人看到學到賺到
更多精彩,請關注我的"今日頭條號":Java雲筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章