Kafka 概述:深入理解架構

臧小晶發表於2020-01-17

本文主要講解 Kafka 是什麼、Kafka 的架構包括工作流程和儲存機制,以及生產者和消費者,最終大家會掌握 Kafka 中最重要的概念,分別是 broker、producer、consumer、consumer group、topic、partition、replica、leader、follower,這是學會和理解 Kafka 的基礎和必備內容。

1. 定義

Kafka 是一個分散式的基於釋出/訂閱模式訊息佇列(Message Queue),主要應用與大資料實時處理領域。

1.1 訊息佇列

Kafka 本質上是一個 MQ(Message Queue),使用訊息佇列的好處? (面試會問)

  1. 解耦:允許我們獨立的擴充套件或修改佇列兩邊的處理過程。
  2. 可恢復性:即使一個處理訊息的程式掛掉,加入佇列中的訊息仍然可以在系統恢復後被處理。
  3. 緩衝:有助於解決生產訊息和消費訊息的處理速度不一致的情況。
  4. 靈活性&峰值處理能力:不會因為突發的超負荷的請求而完全崩潰,訊息佇列能夠使關鍵元件頂住突發的訪問壓力。
  5. 非同步通訊:訊息佇列允許使用者把訊息放入佇列但不立即處理它。

1.2 釋出/訂閱模式

Kafka 概述:深入理解架構

一對多,生產者將訊息釋出到 topic 中,有多個消費者訂閱該主題,釋出到 topic 的訊息會被所有訂閱者消費,被消費的資料不會立即從 topic 清除。

2. 架構

Kafka 概述:深入理解架構

Kafka 儲存的訊息來自任意多被稱為 Producer 生產者的程式。資料從而可以被髮布到不同的 Topic 主題下的不同 Partition 分割槽。在一個分割槽內,這些訊息被索引並連同時間戳儲存在一起。其它被稱為 Consumer 消費者的程式可以從分割槽訂閱訊息。Kafka 執行在一個由一臺或多臺伺服器組成的叢集上,並且分割槽可以跨叢集結點分佈。
下面給出 Kafka 一些重要概念,讓大家對 Kafka 有個整體的認識和感知,後面還會詳細的解析每一個概念的作用以及更深入的原理。

  • Producer: 訊息生產者,向 Kafka Broker 發訊息的客戶端。
  • Consumer: 訊息消費者,從 Kafka Broker 取訊息的客戶端。
  • Consumer Group: 消費者組(CG),消費者組內每個消費者負責消費不同分割槽的資料,提高消費能力。一個分割槽只能由組內一個消費者消費,消費者組之間互不影響。所有的消費者都屬於某個消費者組,即消費者組是邏輯上的一個訂閱者。
  • Broker: 一臺 Kafka 機器就是一個 broker。一個叢集由多個 broker 組成。一個 broker 可以容納多個 topic。
  • Topic: 可以理解為一個佇列,topic 將訊息分類,生產者和消費者面向的是同一個 topic。
  • Partition: 為了實現擴充套件性,提高併發能力,一個非常大的 topic 可以分佈到多個 broker (即伺服器)上,一個 topic 可以分為多個 partition,每個 partition 是一個 有序的佇列。
  • Replica: 副本,為實現備份的功能,保證叢集中的某個節點發生故障時,該節點上的 partition 資料不丟失,且 Kafka 仍然能夠繼續工作,Kafka 提供了副本機制,一個 topic 的每個分割槽都有若干個副本,一個 leader 和若干個 follower。
  • Leader: 每個分割槽多個副本的“主”副本,生產者傳送資料的物件,以及消費者消費資料的物件,都是 leader。
  • Follower: 每個分割槽多個副本的“從”副本,實時從 leader 中同步資料,保持和 leader 資料的同步。leader 發生故障時,某個 follower 還會成為新的 leader。
  • offset: 消費者消費的位置資訊,監控資料消費到什麼位置,當消費者掛掉再重新恢復的時候,可以從消費位置繼續消費。
  • Zookeeper: Kafka 叢集能夠正常工作,需要依賴於 zookeeper,zookeeper 幫助 Kafka 儲存和管理叢集資訊。

3 工作流程

Kafka叢集將 Record 流儲存在稱為 topic 的類別中,每個記錄由一個鍵、一個值和一個時間戳組成。Kafka 是一個分散式流平臺,這到底是什麼意思?

  • 釋出和訂閱記錄流,類似於訊息佇列或企業訊息傳遞系統。
  • 以容錯的持久方式儲存記錄流。
  • 處理記錄流。

Kafka 工作流程

Kafka 中訊息是以 topic 進行分類的,生產者生產訊息,消費者消費訊息,面向的都是同一個 topic。
topic 是邏輯上的概念,而 partition 是物理上的概念,每個 partition 對應於一個 log 檔案,該 log 檔案中儲存的就是 Producer 生產的資料。Producer 生產的資料會不斷追加到該 log 檔案末端,且每條資料都有自己的 offset。消費者組中的每個消費者,都會實時記錄自己消費到了哪個 offset,以便出錯恢復時,從上次的位置繼續消費。

4 儲存機制

Kafka檔案儲存機制

由於生產者生產的訊息會不斷追加到 log 檔案末尾,為防止 log 檔案過大導致資料定位效率低下,Kafka 採取了分片索引機制,將每個 partition 分為多個 segment,每個 segment 對應兩個檔案:“.index” 索引檔案和 “.log” 資料檔案。這些檔案位於同一檔案下,該資料夾的命名規則為:topic 名-分割槽號。例如,first 這個 topic 有三分分割槽,則其對應的資料夾為 first-0,first-1,first-2。

# ls /root/data/kafka/first-0        
00000000000000009014.index    
00000000000000009014.log
00000000000000009014.timeindex
00000000000000009014.snapshot   
leader-epoch-checkpoint
複製程式碼

index 和 log 檔案以當前 segment 的第一條訊息的 offset 命名。下圖為 index 檔案 和 log 檔案的結構示意圖。

Kafka 概述:深入理解架構

“.index” 檔案儲存大量的索引資訊,“.log” 檔案儲存大量的資料,索引檔案中的後設資料指向對應資料檔案中 message 的物理偏移量。

5. 生產者

5.1 分割槽策略

5.1.1 分割槽原因

  • 方便在叢集中擴充套件,每個 partition 可以通過調整以適應它所在的機器,而一個 topic 又可以有多個 partition 組成,因此可以以 partition 為單位讀寫了。
  • 可以提高併發,因此可以以 partition 為單位讀寫了。

5.1.2 分割槽原則

我們需要將 Producer 傳送的資料封裝成一個 ProducerRecord 物件。該物件需要指定一些引數:

  • topic:string 型別,NotNull
  • partition:int 型別,可選
  • timestamp:long 型別,可選
  • key:string型別,可選
  • value:string 型別,可選
  • headers:array 型別,Nullable

(1) 指明 partition 的情況下,直接將給定的 value 作為 partition 的值。
(2) 沒有指明 partition 但有 key 的情況下,將 key 的 hash 值與分割槽數取餘得到 partition 值。
(3) 既沒有 partition 有沒有 key 的情況下,第一次呼叫時隨機生成一個整數(後面每次呼叫都在這個整數上自增),將這個值與可用的分割槽數取餘,得到 partition 值,也就是常說的 round-robin 輪詢演算法。

5.2 資料可靠性保證

為保證 producer 傳送的資料,能可靠地傳送到指定的 topic,topic 的每個 partition 收到 producer 傳送的資料後,都需要向 producer 傳送 ack(acknowledge 確認收到),如果 producer 收到 ack,就會進行下一輪的傳送,否則重新傳送資料。

Kafka 概述:深入理解架構

5.2.1 副本資料同步策略

(1)何時傳送 ack?
確保有 follower 與 leader 同步完成,leader 再傳送 ack,這樣才能保證 leader 掛掉之後,能在 follower 中選舉出新的 leader 而不丟資料。
(2)多少個 follower 同步完成後傳送 ack?
全部 follower 同步完成,再傳送 ack。

同步策略方案比較

5.2.2 ISR

採用第二種方案,所有 follower 完成同步,producer 才能繼續傳送資料,設想有一個 follower 因為某種原因出現故障,那 leader 就要一直等到它完成同步。這個問題怎麼解決?
leader維護了一個動態的 in-sync replica set(ISR):和 leader 保持同步的 follower 集合。當 ISR 集合中的 follower 完成資料的同步之後,leader 就會給 follower 傳送 ack。如果 follower 長時間未向 leader 同步資料,則該 follower 將被踢出 ISR 集合,該時間閾值由 replica.lag.time.max.ms 引數設定。leader 發生故障後,就會從 ISR 中選舉出新的 leader。

5.2.3 ack 應答機制

對於某些不太重要的資料,對資料的可靠性要求不是很高,能夠容忍資料的少量丟失,所以沒必要等 ISR 中的 follower 全部接受成功。
所以 Kafka 為使用者提供了三種可靠性級別,使用者根據可靠性和延遲的要求進行權衡,選擇以下的配置。
(1)ack 引數配置:

  • 0:producer 不等待 broker 的 ack,這提供了最低延遲,broker 一收到資料還沒有寫入磁碟就已經返回,當 broker 故障時有可能丟失資料。
  • 1:producer 等待 broker 的 ack,partition 的 leader 落盤成功後返回 ack,如果在 follower 同步成功之前 leader 故障,那麼將會丟失資料。
  • -1(all):producer 等待 broker 的 ack,partition 的 leader 和 follower 全部落盤成功後才返回 ack。但是在 broker 傳送 ack 時,leader 發生故障,則會造成資料重複。

acks=-1 造成資料重複分析

5.2.4 故障處理細節

Log 檔案中的 HW 和 LEO

LEO:每個副本最大的 offset。
HW:消費者能見到的最大的 offset,ISR 佇列中最小的 LEO。

(1)Follower 故障
follower 發生故障後會被臨時踢出 ISR 集合,待該 follower 恢復後,follower 會 讀取本地磁碟記錄的上次的 HW,並將 log 檔案高於 HW 的部分擷取掉,從 HW 開始向 leader 進行同步資料操作。等該 follower 的 LEO 大於等於該 partition 的 HW,即 follower 追上 leader 後,就可以重新加入 ISR 了。
(2)Leader 故障
leader 發生故障後,會從 ISR 中選出一個新的 leader,之後,為保證多個副本之間的資料一致性,其餘的 follower 會先將各自的 log 檔案高於 HW 的部分截掉,然後從新的 leader 同步資料。
注意:這隻能保證副本之間的資料一致性,並不能保證資料不丟失或者不重複。

5.3 Exactly Once 語義

將伺服器的 ACK 級別設定為-1,可以保證 producer 到 server 之間不會丟失資料,即 At Least Once 語義。相對的,將伺服器 ACK 級別設定為0,可以保證生產者每條訊息只會被髮送一次,即At Most Once 語義。
At Least Once 可以保證資料不丟失,但是不能保證資料不重複;相對的,At Most Once 可以保證資料不重複,但是不能保證資料不丟失。但是,對於一些非常重要的資訊,比如交易資料,下游資料消費者要求資料既不重複也不丟失,即 Exactly Once 語義。
0.11版本的 Kafka,引入了冪等性:producer 不論向 server 傳送多少重複資料,server 端都只會持久化一條。即:

At Least Once + 冪等性 = Exactly Once   
複製程式碼

要啟用冪等性,只需要將 producer 的引數中 enable.idompotence 設定為 true 即可。開啟冪等性的 producer 在初始化時會被分配一個 PID,發往同一 partition 的訊息會附帶 Sequence Number。而 borker 端會對 <PID,Partition,SeqNumber> 做快取,當具有相同主鍵的訊息提交時,broker 只會持久化一條。
但是 PID 重啟後就會變化,同時不同的 partition 也具有不同主鍵,所以冪等性無法保證跨分割槽會話的 Exactly Once。

6. 消費者

6.1 消費方式

consumer 採用 pull(拉取)模式從 broker 中讀取資料。
consumer 採用 push(推送)模式,broker 給 consumer 推送訊息的速率是由 broker 決定的,很難適應消費速率不同的消費者。它的目標是儘可能以最快速度傳遞訊息,但是這樣很容易造成 consumer 來不及處理訊息,典型的表現就是拒絕服務以及網路擁塞。而 pull 模式則可以根據 consumer 的消費能力以適當的速率消費訊息。
pull 模式不足之處是,如果 Kafka 沒有資料,消費者可能會陷入迴圈中,一直返回空資料。因為消費者從 broker 主動拉取資料,需要維護一個長輪詢,針對這一點, Kafka 的消費者在消費資料時會傳入一個時長引數 timeout,如果當前沒有資料可供消費,consumer 會等待一段時間之後再返回,這段時長即為 timeout。

6.2 分割槽分配策略

一個 consumer group 中有多個 consumer,一個 topic 有多個 partition,所以必然會涉及到 partition 的分配問題,即確定哪個 partition 由哪個 consumer 來消費。
Kafka 有兩種分配策略,一個是 RoundRobin,一個是 Range,預設為range,當消費者組內消費者發生變化時,會觸發分割槽分配策略(方法重新分配)。
(1) RoundRobin

roundrobin 分割槽分配

RoundRobin 輪詢方式將分割槽所有作為一個整體進行 hash 排序,消費者組內分配分割槽個數最大差別為1,是按照組來分的,可以解決多個消費者消費資料不均衡的問題。
但是,當消費者組內訂閱不同主題時,可能造成消費混亂,如下圖所示,consumer0 訂閱主題A,consumer1 訂閱主題B,將 A、B主題的分割槽排序後分配給消費者組,TopicB 分割槽中的資料可能分配到 consumer0 中。
roundrobin 分割槽分配-消費者組訂閱多個主題

(2)Range
range 分割槽分配

range 方式是按照主題來分的,不會產生輪詢方式的消費混亂問題。
但是,如下圖所示,consumer0、consumer1 同時訂閱了主題A和B,可能造成訊息分配不對等問題,當消費者組內訂閱的主題越多,分割槽分配可能越不均衡。
range 分割槽分配-消費者組訂閱多個主題

6.3 offset 的維護

由於 consumer 在消費過程中可能會出現斷電當機等故障,consumer 恢復後,需要從故障前的位置繼續消費,所以 consumer 需要實時記錄自己消費到了哪個 offset,以便故障恢復後繼續消費。
Kafka 0.9 版本之前,consumer 預設將 offset 儲存在 Zookeeper 中,從 0.9 版本開始,consumer 預設將 offset 儲存在 Kafka 一個內建的 topic 中,該 topic 為 __consumer_offsets。

剛剛和大家一起深入探討了 Kafka 的架構,比較偏重理論和基礎,這是掌握 Kafka 的必要內容,接下來我會以程式碼和例項的方式,更新 Kafka 有關 API 以及事務、攔截器、監控等高階篇,讓大家徹底理解並且會用 Kafka。如果對你有幫助,點個贊相互鼓勵一下吧~

相關文章