Kafka架構原理
最終大家會掌握 Kafka 中最重要的概念,分别是 Broker、Producer、Consumer、Consumer Group、Topic、Partition、Replica、Leader、Follower,這是學會和了解 Kafka 的基礎和必備内容。
定義
Kafka 是一個分布式的基于釋出/訂閱模式的消息隊列(Message Queue),主要應用與大資料實時處理領域。
消息隊列
Kafka 本質上是一個 MQ(Message Queue),使用消息隊列的好處?(面試會問)
- 解耦:允許我們獨立的擴充或修改隊列兩邊的處理過程。
- 可恢複性:即使一個處理消息的程序挂掉,加入隊列中的消息仍然可以在系統恢複後被處理。
- 緩沖:有助于解決生産消息和消費消息的處理速度不一緻的情況。
- 靈活性&峰值處理能力:不會因為突發的超負荷的請求而完全崩潰,消息隊列能夠使關鍵元件頂住突發的通路壓力。
- 異步通信:消息隊列允許使用者把消息放入隊列但不立即處理它。
釋出/訂閱模式
一對多,生産者将消息釋出到 Topic 中,有多個消費者訂閱該主題,釋出到 Topic 的消息會被所有訂閱者消費,被消費的資料不會立即從 Topic 清除。
架構
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 存儲和管理叢集資訊。
工作流程
Kafka叢集将 Record 流存儲在稱為 Topic 的類别中,每個記錄由一個鍵、一個值和一個時間戳組成。
Kafka 是一個分布式流平台,這到底是什麼意思?
- 釋出和訂閱記錄流,類似于消息隊列或企業消息傳遞系統。
- 以容錯的持久方式存儲記錄流。
- 處理記錄流。
Kafka 中消息是以 Topic 進行分類的,生産者生産消息,消費者消費消息,面向的都是同一個 Topic。
Topic 是邏輯上的概念,而 Partition 是實體上的概念,每個 Partition 對應于一個 log 檔案,該 log 檔案中存儲的就是 Producer 生産的資料。
Producer 生産的資料會不斷追加到該 log 檔案末端,且每條資料都有自己的 Offset。
消費者組中的每個消費者,都會實時記錄自己消費到了哪個 Offset,以便出錯恢複時,從上次的位置繼續消費。
存儲機制
由于生産者生産的消息會不斷追加到 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 檔案的結構示意圖:
“.index” 檔案存儲大量的索引資訊,“.log” 檔案存儲大量的資料,索引檔案中的中繼資料指向對應資料檔案中 Message 的實體偏移量。
生産者
分區政策
分區原因:
- 友善在叢集中擴充,每個 Partition 可以通過調整以适應它所在的機器,而一個 Topic 又可以有多個 Partition 組成,是以可以以 Partition 為機關讀寫了。
- 可以提高并發,是以可以以 Partition 為機關讀寫了。
分區原則:我們需要将 Producer 發送的資料封裝成一個 ProducerRecord 對象。
該對象需要指定一些參數:
- topic:string 類型,NotNull。
- partition:int 類型,可選。
- timestamp:long 類型,可選。
- key:string 類型,可選。
- value:string 類型,可選。
- headers:array 類型,Nullable。
①指明 Partition 的情況下,直接将給定的 Value 作為 Partition 的值。
②沒有指明 Partition 但有 Key 的情況下,将 Key 的 Hash 值與分區數取餘得到 Partition 值。
③既沒有 Partition 有沒有 Key 的情況下,第一次調用時随機生成一個整數(後面每次調用都在這個整數上自增),将這個值與可用的分區數取餘,得到 Partition 值,也就是常說的 Round-Robin 輪詢算法。
資料可靠性保證
為保證 Producer 發送的資料,能可靠地發送到指定的 Topic,Topic 的每個 Partition 收到 Producer 發送的資料後,都需要向 Producer 發送 ACK(ACKnowledge 确認收到)。
如果 Producer 收到 ACK,就會進行下一輪的發送,否則重新發送資料。
①副本資料同步政策
何時發送 ACK?確定有 Follower 與 Leader 同步完成,Leader 再發送 ACK,這樣才能保證 Leader 挂掉之後,能在 Follower 中選舉出新的 Leader 而不丢資料。
多少個 Follower 同步完成後發送 ACK?全部 Follower 同步完成,再發送 ACK。
②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。
③ACK 應答機制
對于某些不太重要的資料,對資料的可靠性要求不是很高,能夠容忍資料的少量丢失,是以沒必要等 ISR 中的 Follower 全部接受成功。
是以 Kafka 為使用者提供了三種可靠性級别,使用者根據可靠性和延遲的要求進行權衡,選擇以下的配置。
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 發生故障,則會造成資料重複。
④故障處理細節
LEO:每個副本最大的 Offset。HW:消費者能見到的最大的 Offset,ISR 隊列中最小的 LEO。
Follower 故障:Follower 發生故障後會被臨時踢出 ISR 集合,待該 Follower 恢複後,Follower 會 讀取本地磁盤記錄的上次的 HW,并将 log 檔案高于 HW 的部分截取掉,從 HW 開始向 Leader 進行同步資料操作。
等該 Follower 的 LEO 大于等于該 Partition 的 HW,即 Follower 追上 Leader 後,就可以重新加入 ISR 了。
Leader 故障:Leader 發生故障後,會從 ISR 中選出一個新的 Leader,之後,為保證多個副本之間的資料一緻性,其餘的 Follower 會先将各自的 log 檔案高于 HW 的部分截掉,然後從新的 Leader 同步資料。
注意:這隻能保證副本之間的資料一緻性,并不能保證資料不丢失或者不重複。
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。
消費者
消費方式
Consumer 采用 Pull(拉取)模式從 Broker 中讀取資料。
Consumer 采用 Push(推送)模式,Broker 給 Consumer 推送消息的速率是由 Broker 決定的,很難适應消費速率不同的消費者。
它的目标是盡可能以最快速度傳遞消息,但是這樣很容易造成 Consumer 來不及處理消息,典型的表現就是拒絕服務以及網絡擁塞。
而 Pull 模式則可以根據 Consumer 的消費能力以适當的速率消費消息。Pull 模式不足之處是,如果 Kafka 沒有資料,消費者可能會陷入循環中,一直傳回空資料。
因為消費者從 Broker 主動拉取資料,需要維護一個長輪詢,針對這一點, Kafka 的消費者在消費資料時會傳入一個時長參數 timeout。
如果目前沒有資料可供消費,Consumer 會等待一段時間之後再傳回,這段時長即為 timeout。
分區配置設定政策
一個 Consumer Group 中有多個 Consumer,一個 Topic 有多個 Partition,是以必然會涉及到 Partition 的配置設定問題,即确定哪個 Partition 由哪個 Consumer 來消費。
Kafka 有兩種配置設定政策,一個是 RoundRobin,一個是 Range,預設為Range,當消費者組内消費者發生變化時,會觸發分區配置設定政策(方法重新配置設定)。
①RoundRobin
RoundRobin 輪詢方式将分區所有作為一個整體進行 Hash 排序,消費者組内配置設定分區個數最大差别為 1,是按照組來分的,可以解決多個消費者消費資料不均衡的問題。
但是,當消費者組内訂閱不同主題時,可能造成消費混亂,如下圖所示,Consumer0 訂閱主題 A,Consumer1 訂閱主題 B。
将 A、B 主題的分區排序後配置設定給消費者組,TopicB 分區中的資料可能配置設定到 Consumer0 中。
②Range
Range 方式是按照主題來分的,不會産生輪詢方式的消費混亂問題。
但是,如下圖所示,Consumer0、Consumer1 同時訂閱了主題 A 和 B,可能造成消息配置設定不對等問題,當消費者組内訂閱的主題越多,分區配置設定可能越不均衡。
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。