刨根問底,Kafka 消息中間件到底會不會丟消息

大型互聯網公司一般都會要求消息傳遞最大限度的不丟失,比如用戶服務給代金券服務發送一個消息,如果消息丟失會造成用戶未收到應得的代金券,最終用戶會投訴。

爲避免上面類似情況的發生,除了做好補償措施,更應該在系設計的時候充分考慮各種異常,設計一個穩定、高可用的消息系統。

認識 Kafka

看一下維基百科的定義

Kafka 是分佈式發佈 - 訂閱消息系統。它最初由 LinkedIn 公司開發,之後成爲 Apache 項目的一部分。

Kafka 是一個分佈式的,可劃分的,冗餘備份的持久性的日誌服務。它主要用於處理活躍的流式數據。

kafka 架構

Kafka 的整體架構非常簡單,是顯式分佈式架構,主要由 producer、broker(kafka)和 consumer 組成。

Kafka 架構(精簡版)

Producer(生產者)可以將數據發佈到所選擇的 topic(主題)中。生產者負責將記錄分配到 topic 的哪一個 partition(分區)中。可以使用循環的方式來簡單地實現負載均衡,也可以根據某些語義分區函數 (如記錄中的 key) 來完成。

Consumer(消費者)使用一個 consumer group(消費組)名稱來進行標識,發佈到 topic 中的每條記錄被分配給訂閱消費組中的一個消費者實例。消費者實例可以分佈在多個進程中或者多個機器上。

Kafka 到底會不會丟失消息?

在討論 kafka 是否丟消息前先來了解一下什麼是消息傳遞語義

消息傳遞語義

message delivery semantic 也就是消息傳遞語義,簡單說就是消息傳遞過程中消息傳遞的保證性。主要分爲三種:

理想情況下肯定是希望系統的消息傳遞是嚴格 exactly once,也就是保證不丟失、只會被處理一次,但是很難做到。

回到主角 Kafka,Kafka 有三次消息傳遞的過程:

  1. 生產者發消息給 Kafka Broker。

  2. Kafka Broker 消息同步和持久化

  3. Kafka Broker 將消息傳遞給消費者。

在這三步中每一步都有可能會丟失消息,下面詳細分析爲什麼會丟消息,如何最大限度避免丟失消息。

生產者丟失消息

先介紹一下生產者發送消息的一般流程(部分流程與具體配置項強相關,這裏先忽略):

  1. 生產者是與 leader 直接交互,所以先從集羣獲取 topic 對應分區的 leader 元數據;

  2. 獲取到 leader 分區元數據後直接將消息發給過去;

  3. Kafka Broker 對應的 leader 分區收到消息後寫入文件持久化;

  4. Follower 拉取 Leader 消息與 Leader 的數據保持一致;

  5. Follower 消息拉取完畢需要給 Leader 回覆 ACK 確認消息;

  6. Kafka Leader 和 Follower 分區同步完,Leader 分區會給生產者回復 ACK 確認消息。

生產者發送數據流程

生產者採用 push 模式將數據發佈到 broker,每條消息追加到分區中,順序寫入磁盤。消息寫入 Leader 後,Follower 是主動與 Leader 進行同步。

Kafka 消息發送有兩種方式:同步(sync)和異步(async),默認是同步方式,可通過 producer.type 屬性進行配置。

Kafka 通過配置 request.required.acks 屬性來確認消息的生產:

kafka producer 的參數 acks 的默認值爲 1,所以默認的 producer 級別是 at least once,並不能 exactly once。

敲黑板了,這裏可能會丟消息的!

Kafka Broker 丟失消息

Kafka Broker 接收到數據後會將數據進行持久化存儲,你以爲是下面這樣的:

消息持久化,無 cache

沒想到是這樣的:

消息持久化,有 cache

操作系統本身有一層緩存,叫做 Page Cache,當往磁盤文件寫入的時候,系統會先將數據流寫入緩存中,至於什麼時候將緩存的數據寫入文件中是由操作系統自行決定。

Kafka 提供了一個參數 producer.type 來控制是不是主動 flush,如果 Kafka 寫入到 mmap 之後就立即 flush 然後再返回 Producer 叫同步 (sync);寫入 mmap 之後立即返回 Producer 不調用 flush 叫異步 (async)。

敲黑板了,這裏可能會丟消息的!

Kafka 通過多分區多副本機制中已經能最大限度保證數據不會丟失,如果數據已經寫入系統 cache 中但是還沒來得及刷入磁盤,此時突然機器宕機或者掉電那就丟了,當然這種情況很極端。

消費者丟失消息

消費者通過 pull 模式主動的去 kafka 集羣拉取消息,與 producer 相同的是,消費者在拉取消息的時候也是找 leader 分區去拉取。

多個消費者可以組成一個消費者組(consumer group),每個消費者組都有一個組 id。同一個消費組者的消費者可以消費同一 topic 下不同分區的數據,但是不會出現多個消費者消費同一分區的數據。

消費者羣組消費消息

消費者消費的進度通過 offset 保存在 kafka 集羣的__consumer_offsets 這個 topic 中。

消費消息的時候主要分爲兩個階段:

1、標識消息已被消費,commit offset 座標;

2、處理消息。

敲黑板了,這裏可能會丟消息的!

場景一:先 commit 再處理消息。如果在處理消息的時候異常了,但是 offset 已經提交了,這條消息對於該消費者來說就是丟失了,再也不會消費到了。

場景二:先處理消息再 commit。如果在 commit 之前發生異常,下次還會消費到該消息,重複消費的問題可以通過業務保證消息冪等性來解決。

總結

那麼問題來了,kafka 到底會不會丟消息?答案是:會!

Kafka 可能會在三個階段丟失消息:

(1)生產者發送數據;

(2)Kafka Broker 存儲數據;

(3)消費者消費數據;

在生產環境中嚴格做到 exactly once 其實是難的,同時也會犧牲效率和吞吐量,最佳實踐是業務側做好補償機制,萬一出現消息丟失可以兜底。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/BJwC3jVGKFp94VG-NENJGw