kafka架構和原理分析

目錄

kafka消費模式

kafka架構

kafka生產者消息發送流程

文件存儲機制

kafka生產者分區策略

kafka數據可靠性與一致性

Exactly Once

kafka消費者分區策略

consumer offset的維護

kafka讀寫效率爲什麼這麼高

zookeeper在kafka中的作用

kafka事務

producer事務

consumer事務


kafka消費模式

kafka是一個分佈式的、基於發佈/訂閱模式的消息隊列。

基於發佈、訂閱模式的消息隊列有兩種消費模式:

一種是pruducer發送數據後,由消息隊列push數據給到consumer,由於消息隊列控制着數據傳輸的速率,而不同consumer消費速率不一致,當消費速率低於生產速率時,consumer就忙不過來了。

kafka採用的另一種消費模式,pruducer把數據push到消息隊列,然後consumer從消息隊列中pull數據。由consumer自己來決定消費速率。當 consumer 速率落後於 producer 時,可以在適當的時間趕上來。這種模式也有其不足之處,在沒有消息時,consumer一直在空輪詢,等待數據的到來。

kafka架構

總體架構圖:

broker:一個kafka服務器就是一個broker,kafka集羣由多個broker組成。一個broker下可以有多個topic

producer:消息生產者,向kafka broker發消息的客戶端,直接發送數據到主分區的broker上,不需要經過任何中間路由

consumer:消息消費者,從kafka broker 中pull消息的客戶端

topic:消息的分類,生產者 和消費者都是面向topic

partition:分區,一個topic可以分爲多個partition,每個partion是一個有序的隊列

replica:partition的副本,kafka爲了防止數據丟失,每個partion會有多個副本,一個Leader和多個Follower,分佈到不同的broker中副本的數量不能超過broker的數量。

leader:partition主分區,producer發送消息和consumer消費消息的對象

follower:從分區,從leader中同步數據,當leader出現故障時,follower會成爲新的leader

consumer group:消費者組,由多個consumer組成。同一消費組內,每個分區只能由某一個消費者消費;多個消費者組可以同時消費。

kafka生產者消息發送流程

生產者消息寫入流程:

  • 1.producer 發送消息給到partition leader
  • 2.leader將數據寫入log
  • 3.follower向leader同步消息,寫入log後,向leader發送ack
  • 4.leader收到follower的ack,向producer發送ack

文件存儲機制

kafka以topic對消息進行分類,一個topic可以有多個partition,每個partition物理上對應一個文件夾,其文件夾的命名
規則爲:topic 名稱+分區序號,例如topicA有3個分區,對應文件夾名稱爲:topicA-0,topicA-1,topicA-2。文件夾中存儲了該partition的所有消息數據和索引文件,producer發送的消息數據存儲在文件夾下的log文件中,producer發送的數據會不斷向log文件追加。

爲了防止log文件過大導致數據訪問效率低下,kafka採取了分片和索引的機制,每個partition分成多個segment,每個segment對應有2個文件:.index 和 .log文件

00000000000000000000.index
00000000000000000000.log
00000000000000184635.index
00000000000000184635.log
00000000000000324567.index
00000000000000324567.log

log數據文件和index索引文件的命名規則:上一個 segment文件最後一條消息的 offset 值進行遞增。
log文件存儲的是massage數據,index文件存儲的是相對offset和position物理偏移量。

  • 相對offset:partition分段之後,每個數據文件的起始offset不爲0,相對offset表示一條message相對於其所屬index文件中起始offset的大小。例如,一個index文件的offset是從10000開始,offset爲10015的message在index文件中的相對offset就是10015-10000 = 15。存儲相對offset可以減小索引文件佔用的空間。
  • position物理偏移量:message在log數據文件中的絕對物理位置,可以理解爲message在數據文件中的指針。

segment index file採取稀疏索引存儲方式,並沒有爲數據文件中的每條message建立索引,每隔一定字節的數據建立一條索引。減少了索引文件佔用空間,可以通過mmap直接在內存當中操作索引,減少系統調用的次數,提高查詢效率。稀疏索引爲數據文件的message設置了一個元數據指針(position),它比稠密索引節省了更多的存儲空間,但查找起來需要消耗更多的時間。

message物理結構:

參數如下:

關鍵字 解釋說明
8 byte offset 在parition(分區)內的每條消息都有一個有序的id號,這個id號被稱爲偏移(offset),它可以唯一確定每條消息在parition(分區)內的位置。即offset表示partiion的第多少message
4 byte message size message大小
4 byte CRC32 用crc32校驗message
1 byte “magic" 表示本次發佈Kafka服務程序協議版本號
1 byte “attributes" 表示爲獨立版本、或標識壓縮類型、或編碼類型。
4 byte key length 表示key的長度,當key爲-1時,K byte key字段不填
K byte key 可選
value bytes payload 表示實際消息數據。

segment下,index索引和log數據文件對應關係如下:

在partition中通過offset查找message的步驟:

  • 1.跟據offset值,通過二分查找法,快速定位到segment對應的index和log文件。
  • 2.根據offset值,在index文件中找出匹配範圍內的偏移量position。
  • 3.得到position,到log文件中,從position處開始順序查找,將每條message的 offset 與目標 offset 進行比較,直到找到爲止

例如,查找offset爲368773的message:

  • 通過二分法找到文件名<=368773的最大segment,如找到了名爲368769的文件
  • 368773-368769=4。在對應的index文件中尋找<=4的最大相對offset對應的position,即 [3,497] 這條記錄
  • 再到log文件中,找到position爲497的message, 按照順序查找,比較每條消息的 offset 是否爲368773,找到後返回

kafka生產者分區策略

1.爲什麼要分區?

  • 更方便的支持集羣橫向擴展,提高併發和負載能力。

2.分區的原則

kafka 是根據message key 來決定message落到哪個分區上的

  • (1)可以手工指定partition,指明partition的值即可
  • (2)沒有指定partition,但是有指定message key的情況下,使用key的hash值與對應topic下的partition數量進行取模運算得到partion的值
  • (3)沒有指定partition,也沒有指定key的情況下,首次調用時隨機生成一個整數(後面持續遞增),用這個整數值和partition的數量取模得到partition的值。kafka默認分區策略就是這種,也叫做round-ribon(輪詢策略)。

kafka數據可靠性與一致性

kafka多分區副本架構是可靠性保證的核心,每個partion多份數據備份存儲在不同機器上,保證消息的持久化。

1.多副本數據同步策略

爲保障producer發送的消息能可靠的發送到指定的topic,topic下的每個partition接收到消息後,都要向producer發送ack,確認收到消息。如果producer收到ack,就會進行下一輪消息發送,否則,重新發送消息。

  • 什麼時候發送ack?
    確保有follower與leader同步成功,leader再發送ack,這樣才能保證leader掛了之後,follower數據的完整性
  • 那麼多少個follower同步成功後發送ack?
    方案一:半數以上的follower同步成功,就發送ack
    方案二:所有follower同步成功後,再發送ack
方案 優點 不足
半數以上完成同步,就發
送ack
延遲低 選舉新的leader 時,容忍n 臺節點的故障,需要2n+1 個副
全部完成同步,才發送
ack
選舉新的leader 時,容忍n 臺節點的故障,需要n+1 個副
延遲高

kafka選用的是第二種方案,對於kafka而言,每個partition都有大量數據,使用方案一,會造成大量數據冗餘,佔用資源成本高

2.同步副本ISR

kafka要確保所有follower完成同步後,leader纔會發送ack,設想:有一個follower,發生故障,遲遲不能與leader 進行同步,那leader 就要一直等下去,這個問題怎麼解決?

這就引出了ISR(in-sync replica set),也叫同步副本,每個分區的leader維護了一個動態的ISR,意爲和leader 保持同步的follower 集
合。只有跟的上leader的follower才能加入ISR,如果follower長時間未向leader 同步數據, 則該follower 將被踢出ISR,該時間閾值由replica.lag.time.max.ms 參數設定。Leader 發生故障之後,就會從ISR 中選舉新的leader。

3.ack 應答機制

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

  • acks參數配置:
    0:producer 不等待broker 的ack,這一操作提供了一個最低的延遲,broker 一接收到還
    沒有寫入磁盤就已經返回,當broker 故障時有可能丟失數據。
    1:produer等待broker的ack,在partition的leader數據持久化到磁盤後返回ack,如果在follower
    同步成功之前leader 故障,那麼將會丟失數據
    -1(all):produer等待broker的ack,在partition的leader和follower都持久化成功後返回ack,
    但是如果在follower 同步完成後,broker 發送ack 之前,leader 發生故障,那麼會
    造成數據重複

4.故障處理

kafka的每個分區副本都有兩個重要的屬性:LEO和HW。注意是所有的副本,不只是leader副本。

  • LEO(log end offset):即日誌末端位移,記錄了該副本log文件中下一條消息的offset
  • HW:高水位,對於同一個副本而已,其HW值都不會大於LEO,表示“已備份狀態”的消息。
    leader副本的HW值也就是分區HW值,代表實際已提交消息的範圍。

kafka有兩套follower副本LEO:

  • 第一套LEO保存在follower副本所在broker中
  • 第二套LEO保存在leader副本所在的brok中,也就是說leader副本機器上保存了所有follower副本的LEO。

那麼爲什麼要保存兩套呢?

因爲kafka要使用第一套LEO幫助follower更新其HW值,使用第二套LEO幫助leader更新HW。

producer 發送消息到partition leader,leader收到消息併成功寫入log後,leader LEO就+1

follower發送fetch請求並得到leader的數據響應,成功寫入log後,follower LEO+1
再比較本地LEO值和leader中HW值,選則小的作爲follower中的HW值

leader收到follower的fetch請求後,拉取對應消息的log,在響應follower前,remote LEO+1(leader上保存的follower LEO)。
leader會篩選出ISR中所有符合條件的partition'副本,比較所有的LEO,選擇最小的LEO值作爲HW。

(1)follower故障

follower發生故障後會被踢出ISR,等到該follower恢復後,會讀取本地磁盤上記錄的HW,將log文件中高於HW的部分截取掉,從HW開始向leader同步,等該follower的LEO大於等於partition的HW,HW,即follower 追上leader 之後,就可以重新加入ISR

(2)leader故障

leader發生故障之後,會從ISR中選出一個新的leader,新的leader會嘗試去更新分區HW,再通知其它的follower把各自的log文件中高於HW部分截取掉,之後從新的leader同步數據

Exactly Once

將kafka的ack級別設置爲-1,可以保證producer到broker之間不會丟失睡覺,即At Least Once。
將kafka的ack級別設置爲0,可以保證生產者每條消息至多發送一次,即At Most Once。

At Least Once可以保證數據不丟失,但不能保證數據不重複。At Most Once 可以保證數據不重複,不能保證數據不丟失。但最好的效果當然是數據不重複也不丟失,即Exactly Once語義。
0.11版本後,kafka推出一重大新特性:冪等性。即同一條消息,不論producer向broker發送多少次數據,broker都只會持久化一條。在ack設置爲-1的前提下,冪等性結合At Least Once 就構成了Exactly Once語義:At Least Once + 冪等性 = Exactly Once

要啓用冪等機制,需要將producer的參數enable.idompotence設置爲true,開啓冪等性後,producer在初始化會分配一個PID,發往同一partition的消息會攜帶Sequence Number,broker對<PID, Partition, SeqNumber>做緩存, 當具有相同主鍵的消息提交時,broker只會持久化一條。

注意:0.11版本之前,在producer客戶端重啓後broker會分配新的PID。

kafka消費者分區策略

kafka的consumer採用pull的方式從broker中拉取消息,如果kafka 沒有數據,消費者可能會陷入循環中,一直返回空數
據。針對這種場景,kafka在消費消息時會傳入一個timeout參數,如果當前沒有可供消費,那麼consumer會等待timeout時長之後再返回。

在kafka中,一個consumer group由一到多個consumer組成。而producer發送給topic的數據是落到多個partition上的,一個partition只能被同一consumer group中的某一個consumer消費。consumer要消費消息,必然面臨partition分配問題。

kafka消費者分區策略有三種:RoundRobin、Range、Sticky。當consumer的數量發生變動的時候,會觸發消費者partition重新分配

  • RoundRobin

RoundRobin就是輪詢的意思,這種策略以consumer group爲整體,拿到consumer group中所有Consumer 訂閱的 TopicPartition後,根據TopicAndPartition的hash值對partition進行排序,按照順序分發給consumer,如果組內每個消費者的topic是同樣的,那麼partition的分配是均勻的。如果組內每個consumer訂閱的topic不相同,可能會造成消費數據混亂(沒有訂閱該topic的consumer卻有可能消費到該topic的消息)

  • Range

Range是以每個topic作爲一個單獨的整體,只有訂閱了該topic的consumer才能消費到該topic的消息。用partition的數量除以consumer的數量,按照平均範圍分配給Consumer,因爲分區數可能無法被消費者數量整除,多出的parition追加到 前面的consumer,可能造成分區分配不均勻。Kafka默認採用RangeAssignor的分配算法

  • Sticky

Kafka從0.11 版本開始引入Sticky,主要有兩大特性:
1.主題分區的分配要儘可能的均勻;
2.當Rebalance 發生時,儘可能保持上一次的分配方案。
這樣一種實現可以使消息分配更加均勻,減少了不必要的操作節約系統資源。

consumer offset的維護

由於consumer在消費過程可能會出現宕機等故障,爲了保證consumer在恢復後,能從上一次消費的位置繼續消費,kafka需要把消費的offset存儲起來。offset是以group+topic+partition存儲的,在0.9版本以前,offset是存儲在zookeeper中的,0.9版本之後,offset是存儲在內部一個topic中的,該topic 爲__consumer_offsets

要消費kafka內置topic需要修改配置文件consumer.properties:exclude.internal.topics=false

kafka讀寫效率爲什麼這麼高

kafka數據是以文件的形式存儲在磁盤中的,是如何做到如此高效的呢?
Kafka讀寫效率高的祕訣在於,它把所有的消息都存儲在文件中。通過mmap提高I/O寫入速度,寫入數據的時候它是末尾添加所以速度很快;同時爲了減少磁盤的寫入的次數,broker會將消息暫存到buffer中,當消息數量到達一定閾值的時候,再flush到磁盤,這樣更減少了磁盤IO的調用次數。而consumer端也是批量fetch多條消息,讀取數據的時候配合sendfile直接輸出

  • 順序寫

kafka生產消息使用的是順序寫,數據進入到分區的消息隊列尾部,這樣的磁盤順序寫比傳統的BTREE隨機寫性能高了很多。磁盤順序寫的速度甚至比內存隨機寫都快。
消費者與生產者互相不干擾,消費者讀取消息隊列的頭部,生產者讀取消息隊列的尾部。 這樣的方式無寫鎖,讀鎖。性能非常高。

  • MMAP

mmap:內存映射文件
寫入數據:應用程序調用了mmap()之後,會打通用戶空間和內核空間,用戶空間和內核空間共享一塊緩衝區,並且MMAP直接映射到磁盤上的某個文件,完成映射之後對物理內存的操作會被同步到硬盤上
客戶端發送數據到達系統內核緩衝區,數據從內核拷貝到用戶空間程序,程序處理完之後,直接將數據放入共享緩衝區,寫入到磁盤

如果沒有MMAP,要寫入數據,客戶端先發送數據到達系統內核,數據從內核拷貝到用戶空間程序,程序處理完之後,會調用系統內核的write寫,數據先寫入到內存,再落到磁盤上

  • 零拷貝

讀取數據:用戶空間調用系統內核sendfile指令,內核讀取到數據後直接通過網卡發送給客戶端。

如果不使用零拷貝技術,要讀取數據,用戶空間程序先調用系統內核的read指令,數據從磁盤上拷貝到內存,內核讀取數據,拷貝到用戶空間程序,程序調用內核write寫將數據通過網卡發送給客戶端

zookeeper在kafka中的作用

kafka集羣是分佈式部署,broker之間相互獨立,這個時候就需要有一個系統來管理broker節點,這個時候就用到了zookeeper。所有的kafka broker節點一起去zookeeper上註冊一個臨時節點,只有一個broker能夠註冊成功,這個broker會成功kafka集羣的Controler,負責管理集羣broker 的上下線。負責topic分區副本分配,leader選舉等工作。

kafka事務

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

producer事務

爲了實現跨分區會話的事務,需要引入一個全局唯一的Transaction ID,這個Transaction ID是由客戶端定義並傳入的,再將producer的PID和Transaction ID綁定,這樣一來,producer在重啓後,就能通過正在進行的Transaction ID獲得原來的PID,而不用重新向broker申請一個PID,通過這種機制,kafka就能保證跨分區跨會話的Exactly Once

爲了管理Transaction ,kafka引入了新組件Transaction Coordinator,Producer和Transaction Coordinator交互,獲得Transaction ID對應的任務狀態,並且Transaction Coordinator還負責將事務寫入kafka內部的一個topic,即使整個服務重啓,由於事務狀態保存起來了,進行中的事務狀態還是可以得到恢復,繼續進行下去

consumer事務

對於consumer而言,事務的保證相對弱些,尤其是無法保證commit的消息被精確消費,這是由於consumer可以通過offset訪問任意消息,而不同segment file的生命週期不一樣,同一事務的消息可能會出現重啓後被刪除的情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章