Kafka 總結學習

CodeDancing發表於2021-06-26

Kafka Need No Keeper

最近在鵝廠工作中不斷接觸到Kafka,雖然以前也使用過,但是對其架構和發展過程總是模模糊糊,所以在回學校準備末考的時候找些資料總結一下。
Kafka Need No Keeper 是一個在Kafka Submit分享的標題,我也是看了Kafka needs no Keeper(關於KIP-500的討論)這篇部落格分享後才對Kafka有了初期的認識,如果想要了解細節的話可以直接閱讀該部落格分享,本篇部落格是一次對Kafka的自我總結,多少有些大白話和概括之意。

Kafka架構

Kafka是什麼?Apache Kafka 是一款分散式流處理框架(新版本後,定位發生了改變),用於實時構建流處理應用。
Kafka的架構可以簡單分為Client和Broker兩部分。在Kafka發展過程中,Kafka都是不斷減少這兩部分對Zookeeper的依賴。
那為什麼要減少對Zookeeper的依賴呢?

  • Kafka在新版本後定位變成了分散式流處理框架,但是本質上還是一個訊息中介軟體,中介軟體與中介軟體之間不應該存在依賴關係,需要降低耦合。
  • Kafka與Zookeeper不斷通訊,不斷寫入資料,而Zookeeper一致性要求較高,當某個資料節點資訊發生變更時,會通知其他節點同步更新,半數以上完成更新才能返回,寫入效能較差,影響了Kafka的效能。

Client架構

Client一般分為三類,Consumer Client、Producer Client和Admin Tool。

舊版架構

  • Producer Client 只需要向Kafka叢集中傳送訊息,不需要連線Zookeeper
  • Consumer Client 需要讀取某主題某分割槽內的訊息,那麼需要知道讀取哪條訊息(讀取offset)和下一次讀哪條訊息(提交offset),所以需要和Zookeeper互動(offset儲存在ZK中)
  • Admin Tool 執行主題的操作,因為後設資料儲存在ZK中,所以需要與ZK互動
Kafka 總結學習

可以看出,Zookeeper在Kafka中①儲存後設資料

新版架構

新版主要針對舊版中的Consumer Client和Admin Tool改進

  • Offset改進:在Kafka中新建一個內部主題_consumer_offset用來儲存消費者組的offset,提交和獲取offset都可以直接與Kafka叢集互動獲取。
  • Rebalance改進:在舊版架構中,消費者組中的消費者消費的主題分割槽資訊都是儲存在ZK中,在新版架構改進中,每一個消費組使用一個Coordinator來控制重分割槽過程。
  • Admin改進:社群引入了新的運維工具AdminClient以及相應的CreateTopics、DeleteTopics、AlterConfigs等RPC協議,替換了原先的Admin Tool,這樣建立和刪除主題這樣的運維操作也完全移動Kafka這一端來做。
Kafka 總結學習
Question Answer
重分割槽是什麼? 如上圖,重分割槽就是將消費者組裡訂閱主題下的分割槽重新分配給當前組內消費者例項的過程。
重分割槽發生條件是什麼? ①消費者組消費者數量改變;
②訂閱的主題數量改變;
③訂閱的主題下分割槽數量改變。
怎麼進行重分割槽? 真正的重分割槽是有Group Leader來完成的。
第一個進入Consumer Group的消費者例項為leader,它向Coordinator申請消費者組成員列表,然後按照分割槽策略進行分割槽,接著將分割槽的結果告訴Coordinator,最後由Coordinator告知所有的消費者分割槽資訊。
Coordinator是怎麼找到的 消費者組向任意一個Broker傳送groupCoordinatorRequest請求,叢集返回一個負載最小的Broker節點使其成為當前消費者組的Coordinator。
分割槽策略是什麼? ①Range分割槽(預設):分塊分割槽,對於每一個主題而言, 首先將分割槽按數字順序排行序,消費者按名稱的字典序排序,然後用分割槽總數除以消費者總數。如果能夠除盡,平均分配;若除不盡,則位於排序前面的消費者將多負責一個分割槽。
②RoundRobin分割槽:輪詢分割槽,對所有主題而言,首先將所有主題的分割槽組成列表,然後按照列表重新輪詢分配分割槽給不同的消費者。

Broker架構

現階段架構

在現階段結構中,Broker端是嚴重依賴Zookeeper的,基本上所有後設資料資訊和管理都要通過Zookeeper叢集,如下圖:

Kafka 總結學習

可以看出,Zookeeper在Kafka中有②叢集管理和③選舉Controller的作用

發展中的架構

第一步首先是隔離非Controller端對ZK的依賴;
第二步是移除Controller端對ZK的依賴,這一步可以採用基於Raft的共識演算法來做(?)。

Kafka 總結學習

Kafka同步副本管理

基本概念

概念 簡介
LEO Log End Offset。日誌末端位移值或末端偏移量,表示日誌下一條待插入訊息的 位移值。
LSO Log Stable Offset。這是 Kafka 事務的概念。如果你沒有使用到事務,那麼這個值無意義。該值控制了事務型消費 者能夠看到的訊息範圍。它經常與 Log Start Offset,即日誌起始位移值相混淆,因為 有些人將後者縮寫成 LSO,這是不對的。在 Kafka 中,LSO就是指代 Log Stable Offset。
HW 高水位值(High watermark)。這是控制消費者可讀取訊息範圍的重要欄位。一 個普通消費者只能“看到”Leader 副本上介於 Log Start Offset 和 HW(不含)之間的 所有訊息。水位以上的訊息是對消費者不可見的。
AR Assigned Replicas。AR 是主題被建立後,分割槽建立時被分配的副本集合,副本個數由副本因子決定。
ISR In-Sync Replicas。Kafka 中特別重要的概念,指代的是 AR 中那些與 Leader 保持同步的副本集合。在 AR 中的副本可能不在 ISR 中,但 Leader 副本天然就包含在 ISR 中。

Kafka檔案大小對應關係:

Kafka 總結學習

ISR

Leader 與 Follower

ISR中的Leader是由Controller指定,與Leader保持同步用指標來衡量就是follower中LEO落後leader中LEO的時間不超過指定時間範圍(replica.lag.time.max.ms=10s)。
(在舊版本中還有另外一個指標是落後的LEO條數,不過這樣子的話每次傳送大量資料後,一開始ISR就只有leader,到後面follower跟上的才能加入ISR,這樣子會導致ZK的頻繁寫入修改效能下降)

另外在Leader掛掉後,Controller會讓ISR中的一個Follower成為Leader,並且開始同步新的Leader的Offset。這裡要注意的是有可能此時ISR中並沒有Follower,所以有兩種選擇,①允許OSR的Follower成為Leader和②該分割槽沒有Leader。這來源於設定unclean.leader.election.enable,設定為true為選擇①,保證了系統的高可用性和損失了一致性,設定為false為選擇②,保證系統的一致性和損失高可用性。

同時一個Leader和多個Follower看上是讀寫分離的結構,但是Kafka並不支援讀寫分離。原因由兩點,①場景不合適,讀寫分離適用於讀負載很大,而寫操作不頻繁的場景,顯然Kafka不是;②同步機制,Follower和Leader之間存在不一致的視窗,很可能出現訊息滯後(類似於幻讀)

ACK機制

這主要決定了Producer傳送資訊時,Kafka的接受機制,有三種:

ACK 機制
ack = 0 at most once,最多一次語義,Producer不需要等待Broker回發確認訊息,直接傳送下一批訊息。
ack = 1 at least onve,最少一次語義,Producer只要Leader成功訊息並且返回確認後,就可以傳送下一批訊息
ack = -1 Producer需要等到Leader和ISR中的Follower同步完成並且返回確認後,才能傳送下一批訊息

那麼問題就來,怎麼實現Exactly Once呢?

Kafka Exactly Once 和事務機制

這裡討論的Exactly Once主要是針對Producer端,至於消費者的Exactly Once可以在客戶端上保留偏移量來實現(參見flink事務機制)。

單Session情況

先來討論單Session的情況,在Kafka中給每個Producer都分配了一個內部的唯一的PID,每次Producer傳送資訊時,帶有的主鍵是<PID ,Topic,Partition,SequenceNumber>,Leader端收到資訊後對相同的<PID,Topic,Partition>的SequenceNumber進行比較,如果來的資訊比Leader端的小,證明資料重複,丟棄該條資訊;如果來的資訊比Leader端的大1,插入該資訊;吐過來的資訊比Leader端的大超過1,證明發生了亂序丟棄該資訊。

跨Session情況

具體內容參考這篇部落格

簡單理解

在單Session的情況如果存在PID都可以保證Exactly Once,那麼要是在不同的Session中我能拿到相同的PID就可以了。所以引入了一個TID(自己定義的)並且繫結了事務一開始的PID,只要事務沒有提交,那麼每次都拿著這個TID去獲取對應的PID就可以保證Exactly Once了。

具體做法

內部引入了一個Transaction Coordinator用於分配PID和管理事務,並且在內建了一個主題Transaction Log用於記錄事務資訊,事務的操作簡圖如下:

Kafka 總結學習
步驟 具體內容
1.請求/返回Transaction Coordinator 由於Transaction Coordinator是分配PID和管理事務的核心,因此Producer要做的第一件事情就是通過向任意一個Broker傳送FindCoordinator請求找到Transaction Coordinator的位置。
2.TID->PID 找到Transaction Coordinator後,具有冪等特性的Producer必須發起InitPidRequest請求以獲取PID。
3 Producer生產訊息 ①Producer拿到PID後向Kafka主題傳送訊息
②Transaction Coordinator會將該<Transaction, Topic, Partition>存於Transaction Log內,並將其狀態置為BEGIN
4 事務完成 ①將PREPARE_COMMIT或PREPARE_ABORT訊息寫入Transaction Log。
②以Transaction Marker的形式將COMMIT或ABORT資訊寫入使用者資料日誌以及_consumer_log中。
③最後將COMPLETE_COMMIT或COMPLETE_ABORT資訊寫入Transaction Log中。

與兩階段提交的區別

  • Kafka事務機制中,PREPARE時即要指明是PREPARE_COMMIT還是PREPARE_ABORT,並且只須在Transaction Log中標記即可,無須其它元件參與。而兩階段提交的PREPARE需要傳送給所有的分散式事務參與方,並且事務參與方需要儘可能準備好,並根據準備情況返回Prepared或Non-Prepared狀態給事務管理器。
  • Kafka事務中,一但發起PREPARE_COMMIT或PREPARE_ABORT,則確定該事務最終的結果應該是被COMMIT或ABORT。而分散式事務中,PREPARE後由各事務參與方返回狀態,只有所有參與方均返回Prepared狀態才會真正執行COMMIT,否則執行ROLLBACK
  • Kafka事務機制中,某幾個Partition在COMMIT或ABORT過程中變為不可用,隻影響該Partition不影響其它Partition。兩階段提交中,若唯一收到COMMIT命令參與者Crash,其它事務參與方無法判斷事務狀態從而使得整個事務阻塞
  • Kafka事務機制引入事務超時機制,有效避免了掛起的事務影響其它事務的問題
  • Kafka事務機制中存在多個Transaction Coordinator例項,而分散式事務中只有一個事務管理器

人生此處,絕對樂觀