【Kafka筆記】3.Kafka架構詳解

1.Kafka工作流程

在這裏插入圖片描述
Kafka中的消息是以topic進行分類的,生產者生產消息,消費者消費消息,都是面向topic的。

topic是邏輯上的概念,而partition是物理上的概念,每個partiyion對應於一個log文件,該log文件存儲的是生產者生產的消息。Producer生產的數據會被不斷的追加到該log文件末尾,切每條數據都有自己的offset。消費者組中的每個消費者,都會實時記錄自己消費到哪個offset,以便在出錯恢復中,從上次的位置繼續消費。

在這裏插入圖片描述

爲防止log文件過大導致數據定位效率低下,每個log文件不會一直追加數據到文件末尾。kafka採用了分片索引機制,將每個partition分爲多個segment(分片)。每個segment對應兩個文件:XXX.indexXXX.log文件。這些文件位於一個文件夾下,該文件夾的命名規則爲:topic名稱+分區序號。例如:demo這個topic有三個分區,則對應的文件夾爲first-0,first-1,first-2

00000000000000000000.index
00000000000000000000.log
00000000000000170410.index
00000000000000170410.log
00000000000000239430.index
00000000000000239430.log

在這裏插入圖片描述

index和log文件以當前segment的第一條消息的offset命名,下圖爲index文件和log文件的結構示意圖:

在這裏插入圖片描述

"index"文件存儲大量的索引信息,"log"文件存儲着大量的數據,索引文件中的元數據指向對應數據文件中message的物理偏移地址。

如何查找到offset=3?

通過offset通過二分查找法確定在哪個索引文件,找到3得到該數據的起始偏移量(索引文件中存儲的是每個消息的起始偏移量和消息大小)就能得到這個消息的起始偏移量和結束偏移量。

2.Kafka生產者

分區策略
  1. 分區的原因

    方便在集羣中擴展,每個partition可以通過調整以適應它所在的機器,而一個topic又可以通過有多個Partition組成,因此整個集羣就可以適應任意大小的數據了。

    提高併發,消費者可以以Partition爲單位讀寫

  2. 分區的原則

    我們需要將producer發送的數據封裝成一個ProducerRecord對象。

    public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value, Iterable<Header> headers)
    public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value)
    public ProducerRecord(String topic, Integer partition, K key, V value, Iterable<Header> headers)
    public ProducerRecord(String topic, Integer partition, K key, V value)
    public ProducerRecord(String topic, K key, V value)
    

    (1) 指明partiton的情況下,直接將指明的值作爲partition的值

    (2) 沒有指明partition的值但有key的情況下,將key的hash值與topic的partition數進行取餘得到partition值(key的哈希分區)

    (3) 既沒有partition值又沒有key值的情況下,第一次調用時隨機生成一個整數(後面每次調用在這個個整數上自增),將這個值與topic可用的partition總數取餘得到partition總數取餘得到partition值,也就是round-robin算法,例如三個分區第一次得到的分區順序爲2,0,1 呢麼以後每次都是2,0,1

數據的可靠性保證

爲了保證producer發送的數據,能可靠的發送到指定的topic,topic的每個partition收到producer發送的數據後,都需要向producer發送ack(acknowledgement 確認收到),如果producer收到ack,就會進行下一輪的發送,否則重新發送數據。

在這裏插入圖片描述

  1. 副本數據同步策略

    方案 優點 缺點
    半數以上完成同步,就發送ack 延遲低 選舉新的leader時,容忍n臺節點的故障,就需要2n+1個副本
    全部完成同步,才發送ack 選舉新的leader時,容忍n臺節點的故障,就需要n+1個副本 延遲高

    Kafka選擇了第二種方案,原因如下:

    1.同樣爲了容忍n臺節點的故障,第一種方案需要2n+1個副本,而第二種方案只需要n+1個副本,而Kafka的每個分區都有大量的數據,第一種方案會造成大量數據的冗餘。

    2.雖然第二種方案的網絡延遲會比較高,但網絡延遲對Kafka的影響較小。

  2. ISR

    採用第二種方案之後,設想以下情景:leader收到數據,所有follower都開始同步數據,但有一個follower,因爲某種故障,遲遲不能與leader進行同步,那leader就要一直等下去,直到它完成同步,才能發送ack。這個問題怎麼解決呢?

    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。

  3. ack應答機制

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

    acks參數配置:

    acks

    0:producer不等待broker的ack,這一操作提供了一個最低的延遲,broker一接收到還沒有寫入磁盤就已經返回,當broker故障時有可能丟失數據

    1:producer等待broker的ack,partition的leader落盤成功後返回ack,如果在follower同步成功之前leader故障,那麼將會丟失數據

    -1(all):producer等待broker的ack,partition的leader和follower全部落盤成功後才返回ack。但是如果在follower同步完成後,broker發送ack之前,leader發生故障,那麼會造成數據重複

  4. 故障處理細節

    在ISR同步中有兩個詞語需要知道:

    LEO:每個副本的最後一個offset
    HW:所有副本中最小的LEO
    

在這裏插入圖片描述

(1) follower 故障

​ follower發生故障後會被臨時剔出ISR,待該follower恢復後,follower會將讀取本地磁盤記錄的上次的HW,並將log文件高於HW的部分截取掉,從HW開始向leader進行同步。等該follower的LEO大於等於該partition的HW,即follpwer追上leader之後,就可以重新加入ISR了。

(2) leader 故障

​ leader發生故障之後,會從ISR中選舉出一個新的leader,之後,爲保證多個副本之間的數據一致性,其餘的follower會先將各自的log文件高於HW的部分截掉,然後從新的leader同步數據

!!!!注意:這隻能保證副本之間的數據一致性,並不能保證數據不丟失或者不重複。在一定情況下還是會發生數據丟失或者重複

Exactly Once 語義

Kafka提供了生產者和消費者之間的擔保語義:

  1. AtLeast Once(至少一次)—— 消息絕不會丟失,但有可能重複,將ACK級別設置爲all
  2. AtMost Once(最多一次)—— 消息可能會丟失,但絕不會重發,將ACK級別設置爲0
  3. Exactly once(精準一次)—— 每個消息傳遞一次且只有一次,開啓冪等性

在0.11版本之前,kafka只能保證數據不丟失,在下游消費者對數據做全局去重,對於多個下游應用的情況,每個都需要單獨做全局去重,對性能造成了很大的影響。

但是在0.11版本之後,加入了冪等性,所謂的冪等性就是指Producer不論向Server發送多少次重複數據,Server端都只會持久化一條數據,冪等性結合AtLeast Once語義就構成了Exactly Once語義,即:

AtLEast Once +冪等性 = Exactly Once

開啓冪等性,只需要enabled.idompotence=true即可,Kafka的冪等性實現其實就是把下游的數據去重放在了broker上,開啓冪等性的Producer在初始化的時候會被分配一個PID,發生同一Partition的消息會附帶Sequence Number(序列號),而Broker端會對<PID,Partition,SeqNumber> 做緩存,這裏的SeqNumber不是Offset,當具有相同主鍵的消息提交時,Broker只會持久化一條。從0.11開始生產者支持使用類似事務的語義將消息發送到多個topic分區的能力:即所有消息都被成功寫入或者沒有。

注意: Producer重啓PID就會變化,同時不同的partition也具有不同的主鍵,所以冪等性無法保證跨分區跨會話的Exactly Once

3. Kafka消費者

消費方式

consumer採用pull(拉)模式從broker中讀取數據。push(推)模式很難適應消費速率不同的消費者,因爲消息發送速率是由broker決定的。它的目標是儘可能以最快速度傳遞消息,但是這樣很容易造成consumer的消費速度趕不上broker的發送速度,從而會導致consumer拒絕服務以及網絡擁塞。而pull模式則可以根據consumer自身的消費能力以適當的消費速率消費消息。

pull模式不足之處:kafka無消息時,消費者陷入循環,一直返回空數據。針對這一點,kafka的消費者在消費數據時會傳入一個時長參數timeout,如果當前沒有數據可供消費,consumer會等待一段時間之後再返回,這段時長就是timeout。

分區分配策略

一個consumer group 中有多個consumer,一個topic可以有多個partition,所以必然會有分區分配問題。一個topic可以被多個消費者組消費,一個消費者組可以消費多個topic,但是每個分區只能被同一個消費者組中的一個消費者消費。

Kafka有兩種分配策略,一是roundrobin(輪詢),一是range(範圍)。

  1. Roundrobin(輪詢策略,按照組劃分)

    假如有三個消費者組,一個topic,七個分區:
    在這裏插入圖片描述

    最後結果爲:

在這裏插入圖片描述

這就是輪詢,但是這只是一個topic主題,如果有多個topic主題訂閱並使用輪詢。就會出現問題。當一個consumer group 消費多個不同的主題的時候,consumer group 會將這些topic以及分區 看成一個整體,然後通過哈希值做排序,然後在進行輪詢。

所以,如果有兩個主題,四個分區,一個消費者組,兩個消費者,就會出現問題了

在這裏插入圖片描述

所以,使用輪詢的前提是消費者組在訂閱同一個主題的分區!!!!

輪詢可以儘量保證分配的分區均衡,最大差別爲1

  1. Range(範圍策略,按照主題劃分)

    該策略是kafka默認的分區策略,按照單個主題分配分區,按照範圍分配分區。

    例如,一個topic,7個分區,3個消費者,一個消費者組

在這裏插入圖片描述

分配完成後:
在這裏插入圖片描述

當有多個topic的時候,就會出現這種情況:
在這裏插入圖片描述

當消費者組內的消費者個數發生變化的時候,就會觸發消費者的重新分配分區!!!

offset的維護

Kafka在0.9版本之前,consumer默認將offset保存在Zookeeper中,從0.9版本開始,consumer默認將offset保存在Kafka一個內置的topic中,該topic爲__consumer_offsets

實例:
創建一個主題,兩個副本,兩個分區
bin/kafka-topics.sh --create --zookeeper master:2181 --topic bigdata --partitions 2 --replication-factor 2
向bigdata生產消息,同時打開一個消費者窗口
bin/kafka-console-consumer.sh --zookeeper master:2181 --topic bigdata --from-beginning這裏必須用zookeeper 纔會把offset保存在zookeeper中。
在這裏插入圖片描述
然後我們去zookeeper查看
[zk: localhost:2181(CONNECTED) 0] ls / [cluster, controller_epoch, controller, brokers, zookeeper, kafka-manager, admin, isr_change_notification, consumers, latest_producer_id_block, config] 這裏面除了zookeeper選項之外其他都是kafka的
controller:指的是和zookeeper交互的broker。誰先和zookeeper建立連接,誰就是conrtoller

[zk: localhost:2181(CONNECTED) 3] get /controller
{"version":1,"brokerid":0,"timestamp":"1573653004493"}
cZxid = 0x288
ctime = Wed Nov 13 21:50:04 CST 2019
mZxid = 0x288
mtime = Wed Nov 13 21:50:04 CST 2019
pZxid = 0x288
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x16e6506ad240000
dataLength = 54
numChildren = 0
[zk: localhost:2181(CONNECTED) 4]

可以看到brokerId是0
brokers:這裏存的都是broker的信息

[zk: localhost:2181(CONNECTED) 4] ls /brokers
[ids, topics, seqid]
[zk: localhost:2181(CONNECTED) 5] ls /brokers/ids
[0, 1, 2]
[zk: localhost:2181(CONNECTED) 6] 

config:kafka的一些配置
consumers:所有的消費者(使用–zookeeper纔會顯示,–bootstrap-server不顯示)

[zk: localhost:2181(CONNECTED) 13] get /consumers/console-consumer-75598/offsets/bigdata/0
1
cZxid = 0x322
ctime = Wed Nov 13 22:35:31 CST 2019
mZxid = 0x326
mtime = Wed Nov 13 22:36:31 CST 2019
pZxid = 0x322
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 1
numChildren = 0
[zk: localhost:2181(CONNECTED) 14] 

呢個1就是存儲的offset ,這是bigdata的分區0,他還有分區1,如果你繼續生產,繼續消費,就會讓這兩個分區的offset輪詢記錄
在這裏插入圖片描述
所以保存在Zookeeper採用的是消費者組_Topic_分區.
這是保存在zookeeper中的形式,呢麼看看offset保存在kafka的例子。

消費記錄保存在Kafka本地:

Kafka 0.9版本之前,consumer默認將offset保存在Zookeeper中,從0.9版本開始,consumer默認將offset保存在Kafka一個內置的topic中,該topic爲__consumer_offsets。

如果想獲取kafka內置主題消息需要配置文件 consumer.properties :
exclude.internal.topics=false
在這裏插入圖片描述
讀取__consumer_offsets主題的內容
這裏使用zookeeper保存offset,是爲了防止該消費者消進程即作爲消費者有作爲生產者,即自己生產自己消費。

運行命令:
./bin/kafka-console-consumer.sh --topic __consumer_offsets --zookeeper master:2181 --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --consumer.config config/consumer.properties --from-beginning

運行截圖:
在這裏插入圖片描述
如果kafka有消費者進程連接kafka。呢麼這裏的日誌就會每隔(1秒,可設置)打印一次,因爲消費者的offset會每隔一秒提交一次

[console-consumer-88458,bigdata,0]::[OffsetMetadata[2,NO_METADATA],CommitTime 1576308184247,ExpirationTime 1576394584247] [console-consumer-88458,bigdata,1]::[OffsetMetadata[1,NO_METADATA],CommitTime 1576308184247,ExpirationTime 1576394584247]
這裏注意一下,這種數據其實也是K-V形式,K表示的是消費者組-主題-分區,V表示的是offset的消費記錄。
這就會出現當在同一消費者組中增加消費者,當該消費者分到了一個分區,該消費者會得到該分區的offset會根據以前的消費記錄接着消費,而不會重新消費。

4.Kafka 高效讀寫數據

1)順序寫磁盤 Kafka 的 producer 生產數據,要寫入到 log 文件中,寫的過程是一直追加到文件末端,
爲順序寫。官網有數據表明,同樣的磁盤,順序寫能到 600M/s,而隨機寫只有 100K/s。這
與磁盤的機械機構有關,順序寫之所以快,是因爲其省去了大量磁頭尋址的時間。
2)零複製技術
零拷貝即從page Cache 直接發送到NIC 省掉了機器底層的數據拷貝。
在這裏插入圖片描述

5. Zookeeper 在 Kafka 中的作用

Kafka 集羣中有一個 broker 會被選舉爲 Controller,負責管理集羣 broker 的上下線,所有 topic 的分區副本分配和 leader 選舉等工作。
Controller 的管理工作都是依賴於 Zookeeper 的。 Controller是誰先和zookeeper建立連接,誰就是conrtoller
Leader選舉 是很重要的一個知識點,這個視頻只是大概說了一下,不是很深入,會另寫一篇博客。
在這裏插入圖片描述

6. Kafka 事務

Kafka 從 0.11 版本開始引入了事務支持。事務可以保證 Kafka 在 Exactly Once 語義的基
礎上,生產和消費可以跨分區和會話,要麼全部成功,要麼全部失敗。

  1. Producer 事務
    爲了實現跨分區跨會話的事務,需要引入一個全局唯一的 Transaction ID,並將 Producer
    獲得的PID和Transaction ID綁定。這樣當Producer重啓後就可以通過正在進行的Transaction
    ID 獲得原來的 PID。
    爲了管理 Transaction,Kafka 引入了一個新的組件 Transaction Coordinator。Producer 就
    是通過和 Transaction Coordinator 交互獲得 Transaction ID 對應的任務狀態。Transaction
    Coordinator 還負責將事務所有寫入 Kafka 的一個內部 Topic,這樣即使整個服務重啓,由於
    事務狀態得到保存,進行中的事務狀態可以得到恢復,從而繼續進行。
  2. Consumer 事務
    上述事務機制主要是從 Producer 方面考慮,對於 Consumer 而言,事務的保證就會相對
    較弱,尤其時無法保證 Commit 的信息被精確消費。這是由於 Consumer 可以通過 offset 訪
    問任意信息,而且不同的 Segment File 生命週期不同,同一事務的消息可能會出現重啓後被
    刪除的情況。

生產者的事務和冪等性主要解決的是精準一致性的問題,加入了事務可以跨分區和跨會話。

發佈了111 篇原創文章 · 獲贊 79 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章