Kafka原理分析之基礎篇

碼頭工人 發表於 2021-05-04

原創文章,轉載請標註。https://www.cnblogs.com/boycelee/p/14728638.html

一、Kafka二、解決問題非同步處理應用解耦流量削峰三、特性讀寫效率網路傳輸併發能力持久化能力可靠性水平擴充套件四、基本概念訊息&批次訊息批次主題&分割槽日誌Log基本概念Log儲存與壓縮日誌儲存日誌壓縮Broker副本生產者消費者消費者組訊息傳遞模式Kafka架構概圖五、核心特性詳解消費者單消費者組多消費者組心跳機制再平衡機制再平衡觸發條件避免再平衡消費者判“死”條件消費者沒有定期地向Coordinator傳送心跳請求規定時間內沒有消費完poll方法返回的訊息避免消費者被判“死”避免被“條件1”判死避免被“條件2”判死位移管理位移主題引入原因訊息格式位移提交自動提交手動提交分割槽副本機制副本機制的優點副本定義副本角色同分割槽多副本,如何保證副本訊息一致性?追隨者副本不對外提供服務的原因同步副本(ISR)與非同步副本(OSR)同步副本的標準HWLEOLeader選舉少部分副本當機全部副本當機為什麼不少數服從多數?物理儲存儲存概述基本概念檔案類別日誌儲存索引訊息壓縮偏移量索引六、參考七、總結

一、Kafka

Kafka是一個分散式的訊息系統。

二、解決問題

訊息系統通常被應用於非同步處理、應用解耦、流量削峰、訊息通訊等場景。

非同步處理

image-20210315230129937
image-20210315230129937

生產者將訊息寫入訊息佇列中,消費者非同步拉取訊息佇列訊息,從而提升訊息處理能力。

應用解耦

image-20210315230734421
image-20210315230734421

Kafka作為訊息傳遞的媒介,各子系統只需要做系統責任內的事情。生產者-消費者模式,Kafka就是訊息佇列。

流量削峰

image-20210315232052783
image-20210315232052783

正常情況下,上游服務(如報價、營銷等)常年流量較大,面對大流量時能夠較為從容地應對,但下游應用(如:交易、訂單等)由於常年流量較小,面對大流量時會因為準備不足,而導致系統被打垮,引發雪崩。

為了應對這一問題,可以利用訊息佇列作為臨時資料儲存節點,消費者根據自身消費能力,通過拉取的方式控制消費速度,達到流量削峰的目的。

三、特性

讀寫效率

Kafka在面對大流量資料時,能夠高效地處理訊息的儲存與查詢。通過軟體設計避免硬體讀取磁碟的效能瓶頸。

網路傳輸

批量讀取訊息,對訊息進行批量壓縮,從而提升網路利用率。

併發能力

Kafka支援訊息分割槽,每個分割槽內保證訊息的順序性,多分割槽之間能夠支援併發操作,提升Kafka併發操作。

持久化能力

Kafka將訊息持久化至硬碟。網路傳輸不可靠,所以需要將資料進行持久化。其中利用了零拷貝、順序讀、順序寫、頁快取等技術使Kafa具備高吞吐特性。

可靠性

支援分割槽多副本,Leader副本負責讀寫,Follow副本只負責同步Leader副本資料,實現訊息冗餘備份,提升Kafka容災能力。

水平擴充套件

多Producer、Broker、Consumer,均為分散式,多Consumer可以加入同一Consumer Group,每個分割槽只能分配一個Consumer,當Kafka服務端增加分割槽數量進行水平擴充套件時,可以向Consumer Group新增Consumer,提升消費能力。當Consumer Group中有Consumer出現故障下線時,能通過再平衡(Rebalance)對分割槽進行再分配。

四、基本概念

訊息&批次

訊息

(1)訊息是Kafka的基本單位;

(2)訊息由key和value的byte陣列構成;

(3)key能夠根據策略將訊息傳送到指定分割槽。

批次

(1)為了提升效率,訊息被分批寫入kafka,同一組訊息必須屬於同一主題的同一分割槽;

(2)分批傳送能夠降低網路開銷,提升傳輸速度。

主題&分割槽

主題(Topic)是用於儲存訊息分類關係的邏輯單元,可以看做儲存訊息的集合。分割槽(partition)是Kafka資料儲存的基本單元,可以看做儲存訊息的集合的子集。Kafka訊息通過主題進行分類,同一Topic的不同分割槽(partition)會分配在不用的Broker上,分割槽機制提供橫向擴充套件的基礎,可以通過增加並在其上分配partition來提升Kafka的訊息並行處理能力。

image-20210417181748356
image-20210417181748356

日誌

Log基本概念

(1)分割槽邏輯上對應一個Log,生產者將訊息寫入分割槽實際是寫入分割槽對應的Log;

(2)Log可以對應磁碟上的資料夾,其由多個Segment組成,每個Segment對應一個日誌檔案和索引檔案;

(3)當Segment大小超出限制時,就會建立新的Segment;

(4)Kafka採用順序I/O,所以只會向最新的Segment追加資料;

(5)索引採用稀疏索引,執行時將其對映至記憶體中,提升索引速度。

image-20210417191546608
image-20210417191546608

Log儲存與壓縮

日誌儲存

(1)時間限制

根據保留時間,當訊息在kafka中儲存的時間超過指定時間,就會被刪除。

(2)大小限制

根據Topic儲存大小,當Topic所佔日誌的大小大於一個閾值,則可以開始刪除最舊的訊息。Kafka會啟動一個新的執行緒,定期檢查是否存在可以刪除的訊息。

日誌壓縮

很多場景中,Kafka訊息的key與value值會不斷變化,就像資料庫中的資料會不斷被修改,消費者只會關心最新的key對應的value。如果開啟日誌壓縮功能,Kafka會開啟執行緒,定時對相同key的訊息進行合併,並保留最新的value值。

Broker

獨立的Kafka服務就是一個broker,broker主要的工作就是接受生產者傳送來的訊息,分配offset並儲存到磁碟中。Broker除了接受生產者傳送的訊息,還處理消費者、其他Broker的請求,根據請求型別進行相應處理行和響應返回。正常情況下一臺機器對應一個broker。

副本

所謂副本就是對訊息程式冗餘備份,分散式系統在不同機器上相互儲存對方資料。在Kafka中,每個分割槽(partition)可以有多個副本,每個副本中的訊息是一樣的(在同一時刻,多臺機器之間的訊息並不完全一致)。

生產者

生產者(Producer)的主要工作是生成訊息。將訊息釋出根據規則推送到Topic的對應分割槽中。例如:(1)對key進行hash;(2)輪詢;(3)自定義。

消費者

消費者(Consumer)的主要工作消費訊息。從對應分割槽中拉取Topic的訊息進行消費。消費者需要通過offset記錄自己的消費位置。

消費者組

多個消費者(Consumer)構成消費者組(Consumer Group)。消費者組(Consumer Group)訂閱的主題(Topic)的每個分割槽只能被分配給,在同一個消費者組中的一個消費者處理。但一個消費者可以消費同一主題(Topic)的多個分割槽。

image-20210418163922518
image-20210418163922518

訊息傳遞模式

image-20210417120121342
image-20210417120121342

​ kafka沒有訊息推送,只有訊息拉取。但消費者可以通過輪詢拉取的方式實現訊息推送功能。

Kafka架構概圖

image-20210316000534417
image-20210316000534417

五、核心特性詳解

消費者

(1)消費者從訂閱的主題消費訊息的偏移量儲存至名字為"__consumer_offsets"的主題中;

(2)推薦使用Kafka來儲存消費者偏移量,zookeeper不適合高併發。

單消費者組

多個消費同一主題的消費者只要將group_id設定相同,就可以組成消費者組。

情況一:一個消費者組中,只有一個消費者。

image-20210418213958556
image-20210418213958556

情況二:消費者組中有多個消費者。

image-20210418213256470
image-20210418213256470

情況三:分割槽數與消費者組數相同。

image-20210418213522999
image-20210418213522999

情況四:消費者組中消費者數量大於分割槽數。閒置的消費者不會接收訊息。

image-20210418214525504
image-20210418214525504

多消費者組

一個主題對應多個消費者組,每個消費者組都能夠消費該主題的所有訊息。

image-20210418215624764
image-20210418215624764

心跳機制

Kafka的心跳機制保證Consumer和Broker之間的健康,當Broker Coordinator正常時,Consumer才會傳送心跳。

再平衡機制

再平衡是規定消費者組下消費者與主題的分割槽之間發生變化時如何分配的協議。

再平衡觸發條件

(1)消費組內消費者發生變化。(消費組數量變化,例如消費組當機退出消費組)

(2)主題對應分割槽數發生變化。(kafka只支援增加分割槽)

(3)訂閱主題發生變化。(消費組使用正規表示式訂閱主題,此時恰好新建了對應主題)

情況一:正常情況,每個分割槽只能分配給一個消費者。

image-20210419000612825
image-20210419000612825

情況二:消費者機器當機,消費者退出消費組,觸發再平衡,重新給消費者組中的消費者分配分割槽。

image-20210419000928251
image-20210419000928251

情況三:Broker機器當機,導致分割槽3無法提供服務。如果分割槽有副本則觸發再平衡,如果沒有副本則消費者3閒置。

image-20210419001510688
image-20210419001510688

情況四:使用正規表示式訂閱主題,當新增主題時,主題對應的分割槽會分配給當前消費者,會觸發再平衡。

image-20210419003311991
image-20210419003311991

避免再平衡

訂閱主題數和主題分割槽數發生變化,一般情況下是運維主動觸發,正常情況下不需要避免再平衡。所以我們可以重點關注由消費者組消費者數量變化而引發的重平衡。

在再平衡完成後,每個消費者例項會定時向Coodinator傳送心跳請求。

消費者判“死”條件
消費者沒有定期地向Coordinator傳送心跳請求

(1)session.timeout.ms引數標識判定消費者死亡的時間閾值。引數預設值為10秒,即如果10秒內沒有收到Group下的某Consumer例項的心跳請求,則被判定該Consumer例項“死亡”,移出Group。

(2)heartbeat.interval.ms引數標識心跳請求傳送的頻率。值越小,Consumer例項傳送心跳請求的頻率就越高。

規定時間內沒有消費完poll方法返回的訊息

(1)max.poll.interval.ms引數標識Consumer例項呼叫poll方法的最大時間間隔。預設值是5分鐘,表示Comsumer如果在5分鐘內無法消費完poll方法返回的訊息,則會被移出Group。

避免消費者被判“死”
避免被“條件1”判死

session.timeout.ms >= 3 * heartbeat.interval.ms。保證Consumer被判死前至少經過3輪心跳請求。

例如:設定 session.timeout.ms = 6s;設定 heartbeat.interval.ms = 2s。

避免被“條件2”判死

儘可能將max.poll.interval.ms時間設定大一些。可以將消費者例項中的最長耗時作為依據,再此基礎之上擴大1-1.5倍。為業務處理留下充足的處理時間,避免由於訊息消費時間過長而導致再平衡。

位移管理

位移主題

Kafka中消費者根據訊息的位移順序消費訊息,消費者的位移由消費者管理,可以儲存在zookeeper中,可以儲存於Kafka主題__consumer_offse中hjmgbknjk.n,jvgnvmnn/.vt。sconsumer_offsets就是位移主題。

引入原因

(1)老版本的位移管理依託Zookeeper,會自動或手動的方式將位移資料提交至Zookeeper進行儲存。當Consumer重啟後,它就能自動從Zookeeper中讀取位移資料,從上次截止消費的地方繼續消費。這種設計是的Kafka Broker不需要儲存位移資料。

(2)但Zookeeper不適合高頻寫操作,所以在0.8.2.x版本後新版本的Consumer推出了全新的位移管理機制。將Consumer的位移資料作為一條普通的Kafka訊息,提交到__consumer_offsets。

(3)正情況下不需要修改它,也不可以隨意地向該主題寫訊息,因為這會導致Kafka無法正常解析。

訊息格式

(1)Key中包含GroupID、主題名、分割槽號;

(2)Value中包含位移值。

位移提交

(1)Consumer需要向Kafka記錄自己的位移資料,這個彙報過程稱為 提交位移(Committing Offsets)

(2)Consumer 需要為分配給它的每個分割槽提交各自的位移資料

(3)位移提交的由Consumer端負責的,Kafka只負責保管。__consumer_offsets

(4)位移提交分為自動提交和手動提交

(5)位移提交分為同步提交和非同步提交

自動提交

(1)設定enable.auto.commit值為true;

(2)通過auto.commit.interval.ms,可以設定自動提交的時間間隔,預設值為5秒;

(3)Kafka會保證在開始呼叫poll方法時,提交上次poll返回的所有訊息的位移資訊。poll方法的邏輯是先提交上一批訊息的位移,再處理下一批訊息,因此它能保證不出現消費丟失的情況;

(4)自動提交會出現訊息重複消費。例:Consumer每 5s提交offset,當提交位移資訊後3秒發生了再平衡,所有Consumer都會從上次提交的offset開始消費,但此時獲取的offset已經是3秒前的offset了,所以我們又會重新消費再平衡前3秒的所有資料。我們只能夠縮小提交offset的時間視窗,但無法避免重複消費。

手動提交

1、同步提交

(1)使用 KafkaConsumer#commitSync():會提交 KafkaConsumer#poll() 返回的最新offset;

(2)該方法為同步操作,等待直到 offset 被成功提交才返回;

1while (true) {
2            ConsumerRecords<String, String> records =
3                        consumer.poll(Duration.ofSeconds(1));
4            process(records); // 處理訊息
5            try {
6                        consumer.commitSync();
7            } catch (CommitFailedException e) {
8                        handle(e); // 處理提交失敗異常
9            }

(3)同步提交會使Consumer處於阻塞狀態;

(4)同步提交在出現異常時會自動重試。

2、非同步提交

(1)使用非同步提交規避Consumer阻塞;

(2)異常(GC、網路抖動)時使用同步提交進行重試。

 1try {
2     while(true) {
3          ConsumerRecords<String, String> records = 
4                                    consumer.poll(Duration.ofSeconds(1));
5          process(records); // 處理訊息
6          commitAysnc(); // 使用非同步提交規避阻塞
7     }
8catch(Exception e) {
9            handle(e); // 處理異常
10finally {
11    try {
12        consumer.commitSync(); // 最後一次提交使用同步阻塞式提交
13    } finally {
14       consumer.close();
15    }

分割槽

分割槽(Partition)是Kafka資料的基本單元。同一個主題(topic)資料會被分散儲存到多個partion中,這些分割槽可以被分配到同一臺機器或不同機器上。優點是有利於水平擴充套件,避免單臺機器在磁碟空間和效能上的限制,同時可以通過複製來增加資料冗餘,從而提升容災能力。為了做到均勻分佈,一般partition的數量一般是Broker Server數量的整數倍。

副本機制

副本機制的優點

分割槽擁有多個副本,提供冗餘資料,有利於確保kafka的高可用性。

副本定義

(1)每個主題可以分為多個分割槽,每個分割槽下配置多個副本;

(2)副本的本質是一個只能追加寫訊息的提交日誌;

(3)同一分割槽下的所有副本儲存相同的訊息序列;

(4)分割槽寫的不同副本分散儲存在不同Broker上,應對Broker當機時分割槽資料不可用的情況。

image-20210501184109411
image-20210501184109411

副本角色

同分割槽多副本,如何保證副本訊息一致性?

最常見的解決方案是基於領導者(Leader-based)的副本機制。

image-20210501190758088
image-20210501190758088

1、副本分為兩類

(1)領導者副本;

(2)追隨者副本。

2、在Kafka的副本機制與其他分散式系統不同

(1)在kafka中,追隨者副本不對外提供服務。所有請求都必須由領導者副本來處理;

(2)追隨者副本的唯一任務就是從領導者副本非同步拉取訊息,並寫入自己的提交日誌中,從而實現與領導者副本的同步。

3、領導者副本所處Broker當機

(1)Kafka依託Zookeeper提供的監控功能能夠感知Broker當機,並開啟一輪新的選舉;

(2)老Leader副本重啟後,只能作為追隨者副本加入叢集中。

追隨者副本不對外提供服務的原因

1、方便實現“Read-your-writes”

(1)生產者使用API想Kafka寫入訊息成功後,能夠立馬使用消費者API檢視到剛才生產的資訊。

(2)如果允許追隨者副本對外提供服務,由於追隨者副本是非同步的,因此就可能出現追隨者副本沒有從領導者副本拉取到最新訊息的情況,就會出現無法立刻讀到最新寫入的訊息。

2、方便實現單調讀(Monotonic Reads)

(1)什麼是單調讀?對於訊息消費者而言,訊息不會時有時無。

(2)如果允許追隨者副本對外提供服務,由於追隨者副本是非同步的,多個副本從領導者副本拉取的訊息不一定同步,就會出現多次請求讀取不同的追隨者副本的情況,資料讀取時有時無。如果讀取全由領導者副本來處理,那麼Kafka就很實現單調讀一致性。

同步副本(ISR)與非同步副本(OSR)

由於追隨者副本需要非同步去拉取領導者副本,那麼我們就需要確定再怎麼樣才算與領導者副本同步。

Kafka引入了In-Sync Replicas,也就是ISR(同步)副本集合,該副本在Zookeeper上維護。如果存在於ISR中則意味著與領導者副本同步,相反則為非同步副本(OSR)

同步副本的標準
image-20210503235617510
image-20210503235617510

(1)replica.lag.time.max.ms引數值標識Follower副本能夠慢於Leader副本的最長時間間隔,預設值為10秒。

(2)若Follower副本落後於Leader副本的最長連續時間間隔不超過該replica.lag.time.max.ms引數值設定的大小,則認定該Follower副本與Leader副本是同步的,否則認定為非同步,會將副本從ISR副本集合中移出(Follower副本的拉取速度慢於Leader副本寫入訊息的速度,且時間間隔超過設定閾值)。

(3)ISR是動態調整集合,非靜態不變的。當Follower副本追上進度時,就會重新被新增會ISR集合。

HW

高水位(HW是High Watermark的縮寫),表示一個特定訊息的偏移量,消費者只能拉取到這個offset之前的資料。

LEO

LEO是Log End Offset的縮寫,表示當前日誌檔案下一條待寫入訊息的offset

image-20210503235536271
image-20210503235536271

Leader選舉

少部分副本當機

(1)當Leader副本對應的broker當機後,就會從Follower副本中選擇一個副本作為Leader;

(2)當當機的broker恢復後就會重新從leader中pull資料。

全部副本當機

unclean.leader.election.enable 控制是否允許 Unclean 領導者選舉。

(1)不開啟Unclean。等待ISR中的一個恢復,並選擇其當leader;(等待時間較長,可用性降低)

(2)開啟Unclean。選擇第一個恢復的副本作為新的leader,無論是否是ISR副本。(開啟會造成資料丟失)

(3)正常情況下建議不開啟,雖然犧牲了高可用性,但維護了資料一致性,避免訊息丟失。

為什麼不少數服從多數?

選擇Leader副本時如果需要超過半數的同步副本同意,演算法所需的冗餘同步副本較多。(一臺機器失敗,就需要3個同步副本)

物理儲存

儲存概述

基本概念

(1)Kafka使用日誌檔案儲存生產者傳送的訊息;

(2)每條訊息都有一個offset值表示它在分割槽中的偏移量;

(3)offset值是邏輯值並不是真實存在的實體地址。其類似於資料庫中的主鍵,唯一標識了資料庫表中的一條資料。而offset在Kafka中的某個分割槽唯一標識一條訊息。

(4)Log與分割槽一一對應,Log並不是一個檔案而是一個資料夾;

(5)資料夾以topicName_pratiitonID命名,分割槽訊息全部都儲存在次資料夾下的日誌檔案中;

(6)Kafka通過分段的方式將Log分為多個LogSegment,LogSegment是邏輯概念,對應磁碟上的Log目錄下的一個日誌檔案和索引檔案;

(7)日誌檔案的命名規則是[baseOffset].log,baseOffset是日誌檔案中第一條訊息的offset;

(8)Kafka日誌是順序追加的;

(9)每個日誌檔案都對應一個索引檔案,索引檔案使用稀疏索引的方式為檔案日誌中部分訊息建立索引。

(10)日誌檔案結構圖

image-20210503153647283
image-20210503153647283

(11)Log示例

image-20210504001658365
image-20210504001658365

建立了一個tp_demo_01的主題,其中存在6個partition,對應的每個partition下存在一個Topic-partition命名的訊息日誌。

(12)LogSegment示例

image-20210504001721206
image-20210504001721206

檔案類別

image-20210504001748640
image-20210504001748640

日誌儲存

索引

為了提升訊息查詢的速度,Kafka從0.8版本開始,為每個日誌檔案新增對應的索引檔案。IndexFile與MassageSet File共同組成了LogSegment。

偏移量索引檔案用於記錄訊息偏移量與實體地址之間的對映關係。時間戳索引檔案則根據時間戳查詢對應的偏移量。

索引檔案中的索引項的格式:每個索引項為8位元組,分為兩部分,第一部分是相對offset(4位元組),即相對於baseOffset的偏移量(baseOffset就是基準偏移量,日誌檔案的命名以基準偏移量來命名)。第二部分是實體地址(4位元組),即其索引訊息在日誌檔案中對應的position位置。通過這兩部分就能實現offset與實體地址之間的對映。

訊息壓縮
image-20210503214424850
image-20210503214424850
偏移量索引
image-20210503214049171
image-20210503214049171

舉例

假設需要查詢startOffset為1067。需要將offset=1067轉換成對應的實體地址,其過程是怎樣的?

(1)將絕對offset轉化為相對offset,絕對offset減去baseOffset,得到相對offset=67;

(2)通過相對offset查詢索引檔案,得到(58,1632)索引項(通過跳錶的方式定位到某一個index檔案,再通過二分法找到不大於相對offset的最大索引項);

(3)從position為1632處開始順序查詢,找到絕對offset=1067的訊息;

(4)最終會得到offset為1070的位置訊息。(因為訊息被壓縮,offset=1067這條訊息被壓縮後一起構成offset=1070這條外層訊息)。

六、參考

《Apache Kafka原始碼剖析》

《極客時間-Kafka核心技術與實戰》

《拉鉤Java-Kafka》

七、總結

本文從場景、特性、基本概念、核心特性等多個維度較為詳細地闡述了Kafka的相關知識。關於kafka穩定性與具體原始碼實現會在進階篇中闡述。

懂得不多,做得太少。歡迎批評與指正!

原創文章,轉載請標註。https://www.cnblogs.com/boycelee/p/14728638.html