服務器宕機了,Kafka 消息會丟失嗎?

大家好,我是樹哥。

消息隊列可謂是高併發下的必備中間件了,而 Kafka 作爲其中的佼佼者,經常被我們使用到各種各樣的場景下。隨着 Kafka 而來得,還有三個問題:消息丟失、消息重複、消息順序。今天,樹哥帶大家聊聊消息丟失的問題。

文章思維導圖

可靠性級別

回到標題提出的問題:我們是否真的能保證 Kafka 消息不丟失?

答案是:我們無法保證 Kafka 消息不丟失,只能保證某種程度下,消息不丟失。

這裏所說的某些情況,從嚴重程度依次爲:Kafka 宕機、服務器宕機、機房地震、城市毀滅、地球毀滅。不要覺得樹哥在危言聳聽,如果你的服務器部署在烏克蘭的首都,那是不是就會遭遇城市毀滅的風險了?因此,我們根據業務的重要程度,設置合理的可靠性級別,畢竟可靠性級別越高,付出的成本越高。

如果你的應用是金融類型或者國民級別的應用,那麼你需要考慮機房地震以上級別的可靠性級別,否則一般考慮到服務器宕機這個維度就可以了。對於機房地震級別以上的情況,大多數都是需要做異地多活,然後做好各地機房數據的實時同步。即使地球毀滅了,你在火星部署了一個機房,其原理也是類似。

我想大多數同學的應用可靠性,可能都只需要考慮到服務器宕機級別,因此後續的考慮也僅限於這個級別。

從大局看 Kafka

要讓 Kafka 消息不丟失,那麼我們必須知道 Kafka 可能在哪些地方丟數據,因此弄清楚 Kafka 消息流轉的整個過程就非常重要了。對 Kafka 來說,其整體架構可以分爲生產者、Kafka 服務器、消費者三大塊,其整體架構如下圖所示。

Kafka 消息架構圖

生產者

對生產者來說,其發送消息到 Kafka 服務器的過程可能會發生網絡波動,導致消息丟失。對於這一個可能存在的風險,我們可以通過合理設置 Kafka 客戶端的 request.required.acks 參數來避免消息丟失。該參數表示生產者需要接收來自服務端的 ack 確認,當收不到確認或者超市時,便會拋出異常,從而讓生產者可以進一步進行處理。

該參數可以設置不同級別的可靠性,從而滿足不同業務的需求,其參數設置及含義如下所示:

  • request.required.acks = 0 表示 Producer 不等待來自 Leader 的 ACK 確認,直接發送下一條消息。在這種情況下,如果 Leader 分片所在服務器發生宕機,那麼這些已經發送的數據會丟失。
  • request.required.acks = 1 表示 Producer 等待來自 Leader 的 ACK 確認,當收到確認後才發送下一條消息。在這種情況下,消息一定會被寫入到 Leader 服務器,但並不保證 Follow 節點已經同步完成。所以如果在消息已經被寫入 Leader 分片,但是還未同步到 Follower 節點,此時Leader 分片所在服務器宕機了,那麼這條消息也就丟失了,無法被消費到。
  • request.required.acks = -1 表示 Producer 等待來自 Leader 和所有 Follower 的 ACK 確認之後,才發送下一條消息。在這種情況下,除非 Leader 節點和所有 Follower 節點都宕機了,否則不會發生消息的丟失。

如上所示,如果業務對可靠性要求很高,那麼可以將 request.required.acks 參數設置爲 -1,這樣就不會在生產者階段發生消息丟失的問題。

Kafka 服務器

當 Kafka 服務器接收到消息後,其並不直接寫入磁盤,而是先寫入內存中。隨後,Kafka 服務端會根據不同設置參數,選擇不同的刷盤過程,這裏有兩個參數控制着這個刷盤過程:

# 數據達到多少條就將消息刷到磁盤
#log.flush.interval.messages=10000
# 多久將累積的消息刷到磁盤,任何一個達到指定值就觸發寫入
#log.flush.interval.ms=1000

如果我們設置 log.flush.interval.messages=1,那麼每次來一條消息,就會刷一次磁盤。通過這種方式,就可以降低消息丟失的概率,這種情況我們稱之爲同步刷盤。 反之,我們稱之爲異步刷盤。與此同時,Kafka 服務器也會進行副本的複製,該 Partition 的 Follower 會從 Leader 節點拉取數據進行保存。然後將數據存儲到 Partition 的 Follower 節點中。

對於 Kafka 服務端來說,其會根據生產者所設置的 request.required.acks 參數,選擇什麼時候回覆 ack 給生產者。對於 acks 爲 0 的情況,表示不等待 Kafka 服務端 Leader 節點的確認。對於 acks 爲 1 的情況,表示等待 Kafka 服務端 Leader 節點的確認。對於 acks 爲 1 的情況,表示等待 Kafka 服務端 Leader 節點好 Follow 節點的確認。

但要注意的是,Kafka 服務端返回確認之後,僅僅表示該消息已經寫入到 Kafka 服務器的 PageCache 中,並不代表其已經寫入磁盤了。這時候如果 Kafka 所在服務器斷電或宕機,那麼消息也是丟失了。而如果只是 Kafka 服務崩潰,那麼消息並不會丟失。

因此,對於 Kafka 服務端來說,即使你設置了每次刷 1 條消息,也是有可能發生消息丟失的,只是消息丟失的概率大大降低了。

消費者

對於消費者來說,如果其拉取消息之後自動返回 ack,但消費者服務在處理過程中發生崩潰退出,此時這條消息就相當於丟失了。對於這種情況,一般我們都是採用業務處理完之後,手動提交 ack 的方式來避免消息丟失。

在我們在業務處理完提交 ack 這種情況下,有可能發生消息重複處理的情況,即業務邏輯處理完了,但在提交 ack 的時候發生異常。這要求消費者在處理業務的時候,每一處都需要進行冪等處理,避免重複處理業務。

能不丟失嗎?

根據我們上面的分析,Kafka 只能做到 Kafka 應用崩潰這個級別,因爲 Kafka 的 acks 僅僅表示寫入了 PageCache。

如果服務器宕機了,即使我們設置了每來一條消息就寫入一次磁盤,那麼也有可能在寫入 PageCache 後、寫入磁盤前這個關鍵點,服務器發生宕機。這時候 PageCache 裏面的消息數據就沒了,那麼消息自然也就丟失了。但如果僅僅是 Kafka 應用崩潰退出,因爲其已經寫入到 PageCache 中了,那麼系統自然會將其寫入到磁盤中,因此消息並不會丟失。

總結

消息可靠性級別,一定是跟業務重要性關聯在一起的。我們無法拋開業務本身的重要性來談可靠性,只能是取一個平衡的值。

根據我的經驗來說,除非是金融類或國民級別的應用,否則只需要考慮到服務器宕機的級別就可以了。而如果是金融級別或國民級別的應用,那麼就需要考慮到城市毀滅的可靠性級別。但地球毀滅這個,我想誰也不會去考慮吧。

對於大多數的應用,考慮服務器宕機級別的情況下,對於 Kafka 消息來說,只需要考慮如下幾個內容即可:

  1. 生產者。 根據業務重要性,設置好 acks 參數,並做好業務重試,以及告警記錄即可。
  2. Kafka 服務端。 根據業務重要性,設置好刷盤參數即可,一般來說都不需要設置成同步刷盤。
  3. 消費者。 使用手動提交 acks 的方式,避免丟失消息,同時需要做好冪等處理,避免重複處理。

本文的思維導圖如下所示。

文章思維導圖

好了,這就是今天的分享了。

如果你喜歡今天的分享,記得一鍵三連支持我!你的鼓勵,是我寫文章最大的動力!

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