kafka 實現原理分析,包括:名詞解釋,與zookeeper關係、controller選舉、leader選舉、消息日誌文件,消息查詢定位

 名詞解釋 

1.broker

Kafka單個節點稱爲broker,一個Kafka服務就是一個broker,多個broker可以組成一個Kafka集羣.

 2.topic (主題)

數據存儲在(log.dir配置的目錄下) 
topic相當於傳統消息系統MQ中的一個隊列queue,producer端發送的message必須指定是發送到哪個topic上.在一個大型的應用系統中,
 可以根據功能的不同,區分不同的topic(訂單的topic,登錄的topic,金額的topic等等)

 3. partition(分區)

數據存儲在(log.dir配置的目錄下)
一個topic可以分爲多個分區p1,p2,p3,每個分區存儲這個topic的一部分數據。p2可以在broker1上,p2、p3可以在broker2上。

一個topic下面可以有多個partition,kafka在接收到message後,會將這個message進行load blance根據(hash(message)%[broker_num])均勻的將這個message分配在不同的partition上。
partition的配置個數一般與kafka的集羣數保持一致即可(即broker的數量),分區即把大文件分成有序的小文件,能夠增加查詢速度。如果每個broker分區數據所在的數據 在不同的磁盤上可以提高IO速度。

   4.partition replica (分區副本,leader和flowwer)

數據存儲在(log.dir配置的目錄下)
副本是把一個分區複製多分放在不同的broker上(注意沒有主本!!所有分區都是副本,副本只是對多個相同分區的 稱呼),一個分區partition 如p1 可以有多個副本,副本數最多爲broker的節點數。副本分爲leader 副本,和flowwer副本。

leader副本查看,flowwer副本查看:

在zookeeper上的/brokers/topics/[topic]/partitions/[partition_id]/state 節點記錄了某partition的leader副本所在brokerId,leader_epoch, ISR集合,zk 版本信息,可以看到此分區的leader分區副本 在broker.id=3的節點上,其中ISR集合(flowwer副本)在broker.id=[3,2]集合的節點中。

 

注意:所有的讀寫都在leader副本上,leader副本可以在任何broker上,flowwer副本也可以在任何broker上

注意:broker的controller控制器節點  , 用來管理 leader副本和flowwer副本!!!

  5. Segment(片斷)

數據存儲在(log.dir配置的目錄下)
partition 在物理結構上可以分爲多個segment,每個segment 上存放着message信息

下圖爲一個分區目錄下的文件。

  6.producer

生產message,發送到topic上。produces採用push的形式推送到broker。

  7.consumer

訂閱指定的topic,消費topic上面的message信息。consumer 根據自己的處理能力,pull下自己訂閱的消息。

  8.Consumer group

多個consumer 可以組成一個consumer group,每個消費者要屬於一個Consumer group,broker中會根據consumer group保存當前group的消息偏移offset,當consumer 啓動時會根據offset返回消息。

注意多個consumer可以組成一個consumer group,對於每條消息consumer group只會消費一次(其中某個consumer消費),不會重複無消費。

controller

Kafka控制器,其實就是一個Kafka系統的Broker。它除了具有一般Broker的功能之外,還具有選舉主題分區Leader節點的功能。在啓動Kafka系統時,其中一個Broker會被選舉爲控制器,負責管理主題分區和副本狀態,還會執行分區重新分配的管理任務。

  如果在Kafka系統運行過程中,當前的控制器出現故障導致不可用,那麼Kafka系統會從其他正常運行的Broker中重新選舉出新的控制器。

AR

簡單來說,分區中的所有副本統稱爲 AR (Assigned Replicas)

ISR

所有與leader副本保持一定程度同步的副本(包括leader副本在內)組成 ISR (In Sync Replicas)

OSR

消息會先發送到leader副本,然後follower副本才能從leader中拉取消息進行同步。同步期間,follow副本相對於leader副本而言會有一定程度的滯後。前面所說的 ”一定程度同步“ 是指可忍受的滯後範圍,這個範圍可以通過參數進行配置。於leader副本同步滯後過多的副本(不包括leader副本)將組成 OSR (Out-of-Sync Replied)由此可見,AR = ISR + OSR。正常情況下,所有的follower副本都應該與leader 副本保持 一定程度的同步,即AR=ISR,OSR集合爲空。

KafkaController的選舉

在Kafka集羣中,每個Broker在啓動時會實例化一個KafkaController類。該類會執行一系列業務邏輯,選舉出主題分區的Leader節點,步驟如下:

  • 第一個啓動的代理節點,會在Zookeeper系統裏面創建一個臨時節點/controller,並寫入該節點的註冊信息,使該節點成爲控制器;
  • 其他的代理節點陸續啓動時,也會嘗試在Zookeeper系統中創建/controller節點,但是由於/controller節點已經存在,所以會拋出“創建/controller節點失敗異常”的信息。創建失敗的代理節點會根據返回的結果,判斷出在Kafka集羣中已經有一個控制器被成功創建了,所以放棄創建/controller節點,這樣就確保了Kafka集羣控制器的唯一性;
  • 其他的代理節點,會在控制器上註冊相應的監聽器,各個監聽器負責監聽各自代理節點的狀態變化。當監聽到節點狀態發生變化時,會觸發相應的監聽函數進行處理。
  • 當控制器被關閉或者與Zookeeper系統斷開連接時,Zookeeper系統上的臨時節點就會被清除。Kafka集羣中的監聽器會接收到變更通知,各個代理節點會嘗試到Zookeeper系統中創建一個控制器的臨時節點。第一個成功在Zookeeper系統中創建的代理節點,將會成爲新的控制器。每個新選舉出來的控制器,會在Zookeeper系統中獲取一個遞增的controller_epoch值。
  •  
  • 參考:https://www.cnblogs.com/smartloli/p/9826923.html

 leader的選舉

最簡單最直觀的方案是,leader在zk上創建一個臨時節點,所有Follower對此節點註冊監聽,當leader宕機時,此時ISR裏的所有Follower都嘗試創建該節點,而創建成功者(Zookeeper保證只有一個能創建成功)即是新的Leader,其它Replica即爲Follower。

實際上的實現思路也是這樣,只是優化了下,多了個代理控制管理類(controller)。引入的原因是,當kafka集羣業務很多,partition達到成千上萬時,當broker宕機時,造成集羣內大量的調整,會造成大量Watch事件被觸發,Zookeeper負載會過重。zk是不適合大量寫操作的。

具體實現過程:

 當Kafka系統實例化KafkaController類時,主題分區Leader節點的選舉流程便會開始。其中涉及的核心類包含KafkaController、ZookeeperLeaderElector、LeaderChangeListener、SessionExpirationListener。

  • KafkaController:在實例化ZookeeperLeaderElector類時,分別設置了兩個關鍵的回調函數,即onControllerFailover和onControllerResignation;
  • ZookeeperLeaderElector:實現主題分區的Leader節點選舉功能,但是它並不會處理“代理節點與Zookeeper系統之間出現的會話超時”這種情況,它主要負責創建元數據存儲路徑、實例化變更監聽器等,並通過訂閱數據變更監聽器來實時監聽數據的變化,進而開始執行選舉Leader的邏輯;
  • LeaderChangeListener:如果節點數據發送變化,則Kafka系統中的其他代理節點可能已經成爲Leader,接着Kafka控制器會調用onResigningAsLeader函數。當Kafka代理節點宕機或者被人爲誤刪除時,則處於該節點上的Leader會被重新選舉,通過調用onResigningAsLeader函數重新選擇其他正常運行的代理節點成爲新的Leader;
  • SessionExpirationListener:當Kafka系統的代理節點和Zookeeper系統建立連接後,SessionExpirationListener中的handleNewSession函數會被調用,對於Zookeeper系統中會話過期的連接,會先進行一次判斷。


參考:https://www.cnblogs.com/smartloli/p/9826923.html

 

與zookeeper的關係

Zookeeper 在 Kafka 中的作用

參考文章:

https://www.jianshu.com/p/a036405f989c

https://www.cnblogs.com/frankdeng/p/9310713.html

1、Broker註冊

Broker是分佈式部署並且相互之間相互獨立,但是需要有一個註冊系統能夠將整個集羣中的Broker管理起來,此時就使用到了Zookeeper。在Zookeeper上會有一個專門用來進行Broker服務器列表記錄的節點:

/brokers/ids

每個Broker在啓動時,都會到Zookeeper上進行註冊,即到/brokers/ids下創建屬於自己的節點,如/brokers/ids/[0...N]。

Kafka使用了全局唯一的數字來指代每個Broker服務器,不同的Broker必須使用不同的Broker ID進行註冊,創建完節點後,每個Broker就會將自己的IP地址和端口信息記錄到該節點中去。其中,Broker創建的節點類型是臨時節點,一旦Broker宕機,則對應的臨時節點也會被自動刪除。

2、Topic註冊

在Kafka中,同一個Topic的消息會被分成多個分區並將其分佈在多個Broker上,這些分區信息及與Broker的對應關係也都是由Zookeeper在維護,由專門的節點來記錄,如:

/borkers/topics

Kafka中每個Topic都會以/brokers/topics/[topic]的形式被記錄,如/brokers/topics/login和/brokers/topics/search等。Broker服務器啓動後,會到對應Topic節點(/brokers/topics)上註冊自己的Broker ID並寫入針對該Topic的分區總數,如/brokers/topics/login/3->2,這個節點表示Broker ID爲3的一個Broker服務器,對於"login"這個Topic的消息,提供了2個分區進行消息存儲,同樣,這個分區節點也是臨時節點。

3、生產者負載均衡

由於同一個Topic消息會被分區並將其分佈在多個Broker上,因此,生產者需要將消息合理地發送到這些分佈式的Broker上,那麼如何實現生產者的負載均衡,Kafka支持傳統的四層負載均衡,也支持Zookeeper方式實現負載均衡。

(1) 四層負載均衡,根據生產者的IP地址和端口來爲其確定一個相關聯的Broker。通常,一個生產者只會對應單個Broker,然後該生產者產生的消息都發往該Broker。這種方式邏輯簡單,每個生產者不需要同其他系統建立額外的TCP連接,只需要和Broker維護單個TCP連接即可。但是,其無法做到真正的負載均衡,因爲實際系統中的每個生產者產生的消息量及每個Broker的消息存儲量都是不一樣的,如果有些生產者產生的消息遠多於其他生產者的話,那麼會導致不同的Broker接收到的消息總數差異巨大,同時,生產者也無法實時感知到Broker的新增和刪除。

(2) 使用Zookeeper進行負載均衡,由於每個Broker啓動時,都會完成Broker註冊過程,生產者會通過該節點的變化來動態地感知到Broker服務器列表的變更,這樣就可以實現動態的負載均衡機制。

4、消費者負載均衡

與生產者類似,Kafka中的消費者同樣需要進行負載均衡來實現多個消費者合理地從對應的Broker服務器上接收消息,每個消費者分組包含若干消費者,每條消息都只會發送給分組中的一個消費者,不同的消費者分組消費自己特定的Topic下面的消息,互不干擾。

5、分區 與 消費者 的關係

消費組 (Consumer Group):
consumer group 下有多個 Consumer(消費者)。
對於每個消費者組 (Consumer Group),Kafka都會爲其分配一個全局唯一的Group ID,Group 內部的所有消費者共享該 ID。訂閱的topic下的每個分區只能分配給某個 group 下的一個consumer(當然該分區還可以被分配給其他group)。
同時,Kafka爲每個消費者分配一個Consumer ID,通常採用"Hostname:UUID"形式表示。

在Kafka中,規定了每個消息分區 只能被同組的一個消費者進行消費,因此,需要在 Zookeeper 上記錄 消息分區 與 Consumer 之間的關係,每個消費者一旦確定了對一個消息分區的消費權力,需要將其Consumer ID 寫入到 Zookeeper 對應消息分區的臨時節點上,例如:

/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]

其中,[broker_id-partition_id]就是一個 消息分區 的標識,節點內容就是該 消息分區 上 消費者的Consumer ID。

6、消息 消費進度Offset 記錄

在消費者對指定消息分區進行消息消費的過程中,需要定時地將分區消息的消費進度Offset記錄到Zookeeper上,以便在該消費者進行重啓或者其他消費者重新接管該消息分區的消息消費後,能夠從之前的進度開始繼續進行消息消費。Offset在Zookeeper中由一個專門節點進行記錄,其節點路徑爲:

/consumers/[group_id]/offsets/[topic]/[broker_id-partition_id]

節點內容就是Offset的值。

7、消費者註冊

消費者服務器在初始化啓動時加入消費者分組的步驟如下

註冊到消費者分組。每個消費者服務器啓動時,都會到Zookeeper的指定節點下創建一個屬於自己的消費者節點,例如/consumers/[group_id]/ids/[consumer_id],完成節點創建後,消費者就會將自己訂閱的Topic信息寫入該臨時節點。

對 消費者分組 中的 消費者 的變化註冊監聽。每個 消費者 都需要關注所屬 消費者分組 中其他消費者服務器的變化情況,即對/consumers/[group_id]/ids節點註冊子節點變化的Watcher監聽,一旦發現消費者新增或減少,就觸發消費者的負載均衡。

對Broker服務器變化註冊監聽。消費者需要對/broker/ids/[0-N]中的節點進行監聽,如果發現Broker服務器列表發生變化,那麼就根據具體情況來決定是否需要進行消費者負載均衡。

進行消費者負載均衡。爲了讓同一個Topic下不同分區的消息儘量均衡地被多個 消費者 消費而進行 消費者 與 消息 分區分配的過程,通常,對於一個消費者分組,如果組內的消費者服務器發生變更或Broker服務器發生變更,會發出消費者負載均衡。

以下是kafka在zookeep中的詳細存儲結構圖:

 

 

日誌文件解讀

關於日誌文件的文章參考 :

https://blog.csdn.net/shujuelin/article/details/80898624

https://blog.csdn.net/zhangxm_qz/article/details/87636094

日誌(消息)文件目錄

分別在server.properties文件中配置log.dirs的存儲路徑,如下爲集羣中配置的broker.id=3的節點。

log.dirs=E:\\kafka1\\kafka_data\\log\\k3

創建topic爲wxl3 兩個分區兩個副本

kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 2 --partitions 3 --topic wxl3

 

 replication-factor 2  表示對分區創建兩個副本,不能超過節點的數量

partitions 3 表示對topic創建三個分區,對於wxl3主題的消息會被均勻的分到這三個分區。

執行完上述創建主題命令在E:\\kafka1\\kafka_data\\log\\k2目錄下可看到建立了三個分區。同時在另一個集羣節點broker.id=2的日誌文件目錄下也看到同樣的三個分區。這樣就topic爲wxl3的就有兩個副本。

兩個副本 的數據完全一致,三個分區中數據完全不同、三分區相加之和等於此topic所有消息。

創建topic爲wxl6 三個個分區1個副本

kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic wxl6

可以看到 ,兩個分區在k2,一個分區在k3 

partition是分段的,每個段叫LogSegment,包括了一個數據文件和一個索引文件,下圖是名爲 wxl3-0 的partition目錄下的文件,可以看到,這個partition有4個LogSegment(剛創建topic的時候只有1個segment,隨着數據的增長會增加新的segment)。

 

 

關於index和log文件的數據格式:

(1)index文件的名稱是此segment中最小的offset,所以我們可以使用二分法快速定位到在哪一個index索引文件中。

(2)index文件中並沒有爲數據文件中的每條Message建立索引,而是採用了稀疏存儲的方式,每隔一定字節的數據建立一條索引。這樣避免了索引文件佔用過多的空間,從而可以將索引文件保留在內存中。但缺點是沒有建立索引的Message也不能一次定位到其在數據文件的位置,從而需要做一次順序掃描,但是這次順序掃描的範圍就很小了。

search
查找某個topic的offset爲7的Message

      1. 首先 客戶端與topic和partition的關係存儲在zookeeper節點中,可以根據這個關係快速定位到哪個分區。

       2. 定位到分區之後,partition目錄下的segment的文件名是消息offset的偏移,我們可以用二分查找確定它是在哪個segment中,自然是在第一個Segment中。

    3. 打開這個Segment的index文件,也是用二分查找找到offset小於或者等於指定offset的索引條目中最大的那個offset。自然offset爲6的那個索引是我們要找的,通過索引文件我們知道offset爲6的Message在數據文件中的位置爲9807。

    4.  打開數據文件,從位置爲9807的那個地方開始順序掃描直到找到offset爲7的那條Message。

    這套機制是建立在offset是有序的。索引文件被映射到內存中,所以查找的速度還是很快的。

一句話,Kafka的Message存儲採用了分區(partition),分段(segment)和稀疏索引這幾個手段來達到了高效性。

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