java的kafka生產消費

毅香雪海發表於2020-12-01

概述

Kafka是一種高吞吐量的分散式釋出訂閱訊息系統,它可以處理消費者在網站中的所有動作流資料。

kafka的相關概念

Broker:Kafka叢集包含一個或多個伺服器,這種伺服器被稱為broker
Topic:每條釋出到Kafka叢集的訊息都有一個類別,這個類別被稱為Topic。(物理上不同Topic的訊息分開儲存,邏輯上一個Topic的訊息雖然儲存於一個或多個broker上但使用者只需指定訊息的Topic即可生產或消費資料而不必關心資料存於何處)
Partition:分割槽Partition是物理上的概念,每個Topic包含一個或多個Partition.建立topic的時候可以指定對應Partition數量
Producer:訊息生產者,負責釋出訊息到Kafka broker
Consumer:訊息消費者,向Kafka broker讀取訊息的客戶端。
Consumer Group:每個Consumer屬於一個特定的Consumer Group(可為每個Consumer指定group name,若不指定group name則屬於預設的group)。

kafka的生產消費流程簡圖:

kafka生產者

kafka生產者是有客戶端實現向kafka服務端寫入檔案的實現。

kafka生產者流程

  • 第一步封裝

    使用ProducerRecord封裝我們的訊息體成一個record,
        在封裝中我們必須要傳入的有:
            記錄的訊息體(value),topic。
        可選的引數有:
            (記錄的時間戳)timestamp、記錄鍵值(key),記錄的請求頭(hearders)

  • 第二步 獲取叢集資料元資訊

    將topic新增到metadata的topics集合中,獲取叢集中分割槽數cluster.partitionCountForTopic(topic),構建ClusterAndWaitTime物件。

  • 第三步 序列化訊息資料

    實現序列。Kafka 提供了預設的序列化機制,也支援自定義序列化。

  • 第四步 呼叫Partitioner.partition方法選擇合適的分割槽

    訊息沒有key則獲取叢集原資料的分割槽後隨機選擇,有key則使用murmur2hash算出分割槽。

  • 第五步 訊息放到訊息累加器

    分好區的訊息不是直接被髮送到服務端,而是放入了生產者的一個快取裡面。在這個快取裡面,多條訊息會被封裝成為一個批次(batch),每一個分割槽裡的資料有序的,預設一個批次的大小是 16K。

  • 第六步 喚醒sender傳送執行緒

    返回的結果RecordAppendResult的 if (result.batchIsFull || result.newBatchCreated)則喚醒sender執行緒。

  • 第七步:Sender 執行緒把一個一個批次傳送到服務端。

生產者流程圖如下:

kafka消費者

 kafka中的消費者和消費者組

消費者

         在java中通常用一個消費執行緒來表示。一個分割槽對應一個消費執行緒。一個分割槽的資料只能被 同一個消費組內 的一個 消費者 消費,而 不能拆給同一個消費者組多個消費者 消費。當其中某一些消費者離開或者的時候,就會進行Partition Rebalance分割槽再均衡,使partition的所有權在消費者之間轉移。

消費者組

       消費者組是一組消費者的集合。消費者用一個消費者組名錶示自己在哪一個消費者組。每個topic中的資料 可以被 多個 消費者 消費,但是每個消費者組消費的資料是 互不干擾 的。所以說,每個 消費組消費的都是完整的資料。

以下是消費者和分割槽的個數消費關係圖:

kafka的消費這的負載均衡演算法

    1. A=(partition數量/同組內消費者總個數) 
    2. M=對上面所得到的A值小數點第一位向上取整 
    3. 計算出該消費者拉取資料的patition合集:Ci = [P(M*i )~P((i + 1) * M -1)]

假設有一個topic有十個pitition。三個消費者。則計算結果為:

A=10/3 = 3.333
M = 4


C0 = [p(0*4),p(0+1)*4-1] = [p0,p3]
C1 = [p(1*4),p(1+1)*4-1] = [p4,p7]
C2 = [p(2*4),p(2+1)*4-1] = [p8,p11]

所以最終的結果是消費者0消費 p0~p3四個分割槽。;消費者1消費 p4~p7四個分割槽;消費者2消費 p8~p10三個分割槽。

消費者提交偏移量

      消費者需要向kafka服務端提交自己的位移資料,告訴服務端自己已經處理到了那個位置的資料。消費者通過_consumer_offset 的特殊主題傳送 訊息,訊息裡包含每個分割槽的偏移量。 偏移量的提交不會影響最新的資料的消費,應為服務端自己也維護了一個每個partition的位置。兩者在所有消費者正常的情況下不會相互影響。所以消費者一直處於執行狀態,偏移量就沒有 什麼用處。不過,如果消費者發生崩潰、有新的消費者加入群組或者停止後從新消費,就會觸發再均衡,再均衡會給每個消費者分配新的分割槽,而不一定是之前處理的那個。為了能夠繼續 之前的工作,消費者需要讀取每個分割槽最後一次提交 的偏移量,然後從偏移量指定的地方 繼續處理。

自動提交偏移量

       自動提交偏移量是在每次消費資料的時候,自動提交上一次消費的記錄。自動體積可以通過設定 enable.auto.commit 為 true,這樣Kafka 會在開始呼叫 poll 方法時,提交上次 poll 返回的所有訊息。從順序上來說,poll 方法的邏輯是先提交上一批訊息的位移,再處理下一批訊息,因此它能保證不出現消費丟失的情況。在預設情況下,Consumer 每 5 秒自動提交一次位移。可以通過修改 auto.commit.interval.ms 的值來改變提交頻率。

       自動提交位移雖然能保證所有的資料都能被消費和處理。但是可能會出現重複消費。如果消費者在還沒有提交偏移量的時候就發生崩潰,那就會導致下一次從新消費的時候會消費到部分崩潰之前以及消費的資料。

手動提交偏移量

        把 auto.commit.offset 設為 false,讓應用程式決定何時提交偏移量。手動提交偏移量分為兩種,一種是同步提交,一種是非同步提交。一般情況下是兩種混合使用。

同步提交偏移量commitSync()

 
while (true) {
      ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
      process(records); // 處理訊息
      try {
           consumer.commitSync();  //提交偏移量
      } catch (CommitFailedException e) {
      }
}

      commitSync() 將會提交由 poll() 返回的最新偏移量 , 所以在處理完所有記錄後要 確保呼叫了 commitSync()。否則還是會有丟失訊息的風險。但是再均衡會導致最近一 批訊息到發生再均衡之間的所有訊息都將被重複處理。在成功提交或碰到無怯恢復的錯誤之前, commitSync() 會一直重試(應用程式也一直阻塞)。

非同步提交偏移量commitAsync()

      同步提交的操作,在提交偏移量和處理資料是單執行緒的,所以放入鍋服務端還沒有返回訊息,那麼麼所有的資料消費和處理都會被阻塞掉。這樣就會影響程式的吞吐量。通過一部提交偏移量來降低提交頻率來提升吞吐量,但如果發生了再均衡, 會增加重複訊息的數量。使用非同步提交 API。我們只管傳送提交請求,無需等待 broker的響應。

 
while (true) {
     ConsumerRecords<String, String> records =
           consumer.poll(Duration.ofSeconds(1));
     process(records); // 處理訊息
     try {
           consumer.commitAsync();  //非同步提交
     } catch (CommitFailedException e) {
                        
     }
}

       commitAsync()t提交在出現問題時它不會自動重試。因為它是非同步操作,倘若提交失敗後自動重試,那麼它重試時提交的位移值可能早已經“過期”或不是最新值了。

參考文件:

http://kafka.apache.org/10/documentation.html
https://baijiahao.baidu.com/s?id=1658221660132943620&wfr=spider&for=pc

https://www.jianshu.com/p/6845469d99e6

 

相關文章