Kafka 學習與使用總結

一、Kafka 簡介

kafka 是一個分佈式流處理平臺,主要適用於以下場景:

  1. 構造實時流數據管道,它可以在系統或應用之間可靠地獲取數據。 (相當於message queue) ;
  2. 構建實時流式應用程序,對這些流數據進行轉換或者影響。 (就是流處理,通過 kafka stream topic 和 topic 之間內部進行變化)。

有如下特點:

  • Kafka作爲一個集羣,運行在一臺或者多臺服務器上;
  • Kafka 通過 topic 對存儲的流數據進行分類;
  • 每條記錄中包含一個 key,一個 value 和一個 timestamp(時間戳)。

提供有 4 個核心的 API:

  • The Producer API 允許一個應用程序發佈一串流式的數據到一個或者多個Kafka topic。
  • The Consumer API 允許一個應用程序訂閱一個或多個 topic ,並且對發佈給他們的流式數據進行處理。
  • The Streams API 允許一個應用程序作爲一個流處理器,消費一個或者多個topic產生的輸入流,然後生產一個輸出流到一個或多個topic中去,在輸入輸出流中進行有效的轉換。
  • The Connector API 允許構建並運行可重用的生產者或者消費者,將Kafka topics連接到已存在的應用程序或者數據系統。比如,連接到一個關係型數據庫,捕捉表(table)的所有變更內容。

二、Kafka 組件

Kafka 集羣中的單臺服務器被稱爲 Broker,Broker 中包含了多個 Partition。Partition 是一個有序的隊列,消息最終將寫入 Partition ,同時它也是 Topic 在物理上的分組。 每個 Topic 代表一類消息,一個 Topic 包含一個或多個 Partition,與數據庫表類似,用戶在發送或讀取消息時需要指定具體的 Topic。Producer 意爲生產者,代表着用戶發送消息的一端,與此對應的是 Consumer,意爲消費者,即接收消息並做出相應處理的一端。
在這裏插入圖片描述

Broker

每臺 Kafka 服務器被稱爲 Broker,多個 Broker 組成了 Kafka 集羣。

Topic & Partition

Topic 就是數據主題,是數據記錄發佈的地方,可以用來區分業務系統。Kafka 中的 Topics 總是多訂閱者模式,一個 Topic 可以擁有一個或者多個消費者來訂閱它的數據。

對於每一個 topic, Kafka 集羣都會維持一個分區日誌,如下所示:

]

每個 Partition 都是有序且順序不可變的記錄集,並且不斷地追加到結構化的 commit log 文件。Partition 中的每一個記錄都會分配一個 id 號來表示順序,我們稱之爲 offset,offset 用來作爲 Partition 中每一條記錄的唯一標識。

segment

Partition 是一個有序的隊列,每個 Partition 都會被映射到一個邏輯的日誌文件之上。這個邏輯的日誌文件由一個或多個被稱爲 segment 的文件組成,分成多個 segment 文件可以有效的控制單個物理文件的大小。同時因爲 Partition 中的消息是有序的,所以每當一個消息被髮送到一個 Partition 之上時,它都會被追加到該 Partition 的邏輯文件中的最後一個 segment 的末尾,如上圖所示。

offset

事實上,在每一個消費者中唯一保存的元數據是 offset(偏移量),即消費者當前消費的消息在 log 中的位置。offset 由消費者控制,通常在讀取記錄後,消費者會以線性的方式增加偏移量,但是實際上,由於這個位置由消費者控制,所以消費者可以採用任何順序來消費記錄。例如,一個消費者可以重置到一箇舊的偏移量,從而重新處理過去的數據;也可以跳過最近的記錄,從"現在"開始消費。 在這裏插入圖片描述

Producer

Producer 意爲生產者,代表着用戶發送消息的一方。用戶在 Producer 一端通過指定具體的 Topic 來發送消息,默認的情況下消息會被隨機發送到該 Topic 下的某個 Partition ,當然我們也可以通過指定具體 Partition 編號,來將消息發送到指定的 Partition 中,這也是保證消息順序的一種方式。

Consuemr

Consuemr 意爲消費者,代表着用戶接收消息並做出相應處理的一方。每個 Consumer 都會屬於一個特定的 Consumer Group,而一個 Consumer Group 則可能包含一個或多個 Consumer,這樣子區分有以下幾個主要的特點:

  1. 正常情況下,同一條消息只會被髮送到同一個 Consumer Group 中的其中一個 Consumer;
  2. Kafka 爲每個 Consumer Group 維護了一個 offset,用於記錄該 Consumer Group 的消費位置;
  3. 可以指定 Consumer Group 下的不同 Consumer 消費不同的 Partition 下的消息;
    在這裏插入圖片描述

Zookeeper

Zookeeper 作爲 Kafka 集羣管理的第三方中間件,其主要作用包括:

  1. Leader 選舉;
  2. 在 Consumer Group 發生變化時進行 Rebalance;
  3. 持久化 Kafka 維護的 Consumer Group 對應的 offset 值;
  4. 其它;

三、問題與應用

消息丟失的問題

在舊的版本中,Producer 的發送類型分爲同步與異步兩種,通過參數 producer.type=[sync | async]進行設置。而在新的版本中去掉了producer.type參數,改用以下方式可得到相同的效果。

在新版本的 Kafka 中,批處理是提升性能的一個主要驅動,爲了允許批量處理,kafka 生產者會嘗試在內存中彙總數據,並用一次請求批次提交信息。通過設置linger.msbatch.size等參數可控制請求間隔時間以及批處理大小等(詳情參見:http://kafka.apachecn.org/documentation.html#producerconfigs)。當設置linger.ms=0時將立即發送消息(默認爲 0),或者設置batch.size=0以禁用批處理。

而在使用 Producer API 發送消息時,使用的是異步發送消息方法,它將在確認發送時調用回掉函數,示例如下:

ProducerRecord<byte[],byte[]> record = new ProducerRecord<byte[],byte[]>("the-topic", "key".getBytes(), "value".getBytes());
producer.send(record,
                new Callback() {
                     public void onCompletion(RecordMetadata metadata, Exception e) {
                         // doing something 
                     }
                });

注:即使異步的情形下,發送到同一分區的記錄的回調也會保證按順序執行。

producer.send方法返回一個java.util.concurrent.Future<RecordMetadata>對象,通過調用Future#get()方法可模擬同步阻塞的效果。

producer.send(record).get()

針對以上情形,可得到以下兩種主要的情形:

  1. 當使用同步阻塞以及禁用批處理髮送消息時,Producer 將會等待回調函數執行後,再繼續執行,且消息爲單條發送;

  2. 當使用異步非阻塞且未啓用批處理髮送消息時,Producer 在調用完發送消息的方法後,將立即執行後續程序,且消息爲批量發送;

同時爲了保證發送消息的可靠性,在 Producer 端可通過參數acks配置在確認一個請求發送完成之前需要收到的反饋信息的數量,其中:

  • acks=0 如果設置爲0,則 producer 不會等待服務器的反饋。該消息會被立刻添加到 socket buffer 中並認爲已經發送完成。在這種情況下,服務器是否收到請求是沒法保證的,並且參數retries也不會生效(因爲客戶端無法獲得失敗信息)。每個記錄返回的 offset 總是被設置爲-1。
  • acks=1 如果設置爲1,leader節點會將記錄寫入本地日誌,並且在所有 follower 節點反饋之前就先確認成功。在這種情況下,如果 leader 節點在接收消息之後,並且在 follower 節點複製數據完成之前產生錯誤,則這條消息會丟失。
  • acks=all || -1 如果設置爲all,這就意味着 leader 節點會等待所有同步中的副本確認之後再確認這條記錄是否發送完成。只要至少有一個同步副本存在,記錄就不會丟失。這種方式是對請求傳遞的最有效保證。acks=-1與acks=all是等效的。

基於以上配置情形,有如下情形可能發生消息丟失:

  1. 當異步發送消息時, Producer 不會等待服務器的反饋,如果網絡發生異常或其它情況,則可能會丟失消息;
  2. 當異步批量發送消息時,如果 Producer down 掉,則緩衝區消息可能丟失;
  3. acks=0時,Producer 不會等待服務器的反饋,如果網絡發生異常或其它情況,可能會丟失消息;
  4. acks=1時,如果 leader 節點在接收到消息之後,並且在 follower 節點複製數據完成之前產生錯誤,則這條消息會丟失;

所以如果需要保證消息不丟失,需要滿足以下條件:

  1. 同步阻塞方式發送消息
  2. 禁用批處理,立即發送消息
  3. 設置 acks=-1或者acks=all

消息重複消費的問題

在 Consumer 中,offset 提交的方式有兩種:

  1. 自動提交;
  2. 手動提交;

在新的版本中,通過 enable.auto.commit=[true || false](默認爲 true)以及auto.commit.interval.ms(默認=5000)參數來控制是否由 Consumer 自動在後臺提交 offset 以及自動提交 offset 的頻率(以毫秒爲單位),而在舊的版本中,則通過auto.commit.enable=[true || false](默認爲 true)以及auto.commit.interval.ms(默認=60 * 1000)參數來達到同樣的效果。

當 Consumer 在消費完數據並提交 offset 之後,offset 將被持久化在 zookeeper 之中。如果程序發生異常或重啓,那麼它將接着上一次的 offset 繼續消費消息。所以如果當 Consumer 消費完消費之後,卻在提交 offset 時發生異常,那麼將可能導致消息被重複消費,根據消息重複消費的數量,可分爲以下情形:

  1. 自動提交 offset 時,由於 offset 不會立即提交,所以可能會造成單次異常卻重複消費多條連續的消息;
  2. 手動提交 offset 時,如果選中每消費一條消息,都手動提交一次 offset,那麼針對每個分區來講,單次異常只會至多重複消費一條消息;

通常而言,如果我們需要保證消息是全局或以鍵爲單位的順序消息時,選擇手動提交 offset 會是更保險的做法。

順序消息

保證消息的順序入隊與消費,通常分爲兩種情況:

  1. 全局有序,比如操作 【OP_1(id=1), OP_2(id=2), OP_3(id=1)】,它們的消費順序也必須是 【OP_1, OP_2, OP_3】 ;
  2. 以 key 爲單位的有序,比如以上操作,允許它們的操作爲【OP_1, OP_3,OP_2】;

Kakfka 保證以 Partition 爲單位的分區有序,所以如果選擇全局有序,那麼只能選擇單個分區寫入,以及如果消費者如果需要保證異常重啓後也嚴格按照之前的順序消費,那麼也僅能使用單線程消費且手動提交 offset 的方式。但是好在實際的業務中,更多的是保證以 key 爲單位的消息有序,所以我們可以通過將數據發送至多個 Partition,以提高程序的併發量,只要保證相同的 key 在同一個分區即可。

綜合消息丟失與重複消費的問題,如果我們需要實現一個可靠的且保證以 key 爲單位的有序消息,且消費者也嚴格按照順序消費的程序,那麼必須保證以下條件:

  1. Producer 端同步發送消息,且反饋的配置爲acks=-1 || all
  2. 相同的 key 寫入相同的 Partition;
  3. Consumer 使用手動提交 offset,每消費完一個消息後,手動提交 offset;
  4. 做好冪等消費操作,因爲重複消費的問題理論上不可避免;

以上。

四、參考文檔

此爲學習與使用 Kafka 筆記,歡迎指正。

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