Kafka入門教程其二 生產與消費詳解 Rebalance過程 leader選舉過程

1. 概述

接着上一篇博客,本篇主要介紹Kafka的生產與消費的過程。Producers往Brokers裏面的指定Topic中寫消息,Consumers從Brokers裏面拉去指定Topic的消息。

圖中有兩個topic,topic 0有兩個partition,topic 1有一個partition,三副本備份。

2. 生產


創建一條記錄,記錄中一個要指定對應的topicvaluekeypartition可選。 先序列化,然後按照topic和partition,放進對應的發送隊列中。kafka produce都是批量請求,會積攢一批,然後一起發送,不是調send()就進行立刻進行網絡發包。

如果partition沒填,那麼情況會是這樣的:

  • key有填
    按照key進行哈希,相同key去一個partition。(如果擴展了partition的數量那麼就不能保證了)
  • key沒填
    round-robin來選partition

這些要發往同一個partition的請求按照配置,攢一批然後由一個單獨的線程一次性發過去。

2.1 partition分配與Leader選舉

當存在多副本的情況下,會盡量把多個副本,分配到不同的broker上。kafka會爲partition選出一個leader,之後所有該partition的請求,實際操作的都是leader,然後再同步到其他的follower。當一個broker歇菜後,所有leader在該broker上的partition都會重新選舉,選出一個leader。(這裏不像分佈式文件存儲系統那樣會自動進行復制保持副本數)

然後這裏就涉及兩個細節:怎麼分配partition,怎麼選leader。

關於partition的分配,還有leader的選舉,總得有個執行者。在kafka中,這個執行者就叫controller。kafka使用zk在broker中選出一個controller,用於partition分配和leader選舉。

2.1.1 partition分配
  1. 將所有Broker(假設共n個Broker)和待分配的Partition排序
  2. 將第i個Partition分配到第(i mod n)個Broker上 (這個就是leader)
  3. 將第i個Partition的第j個Replica分配到第((i + j) mode n)個Broker上
2.1.2 Leader選舉

controller會在Zookeeper的/brokers/ids節點上註冊Watch,一旦有broker宕機,它就能知道。當broker宕機後,controller就會給受到影響的partition選出新leader。controller從zk 的/brokers/topics/[topic]/partitions/[partition]/state中,讀取對應partition的ISR(in-sync replica已同步的副本)列表,選一個出來做leader。

選出leader後,更新zk,然後發送LeaderAndISRRequest給受影響的broker。這裏不是使用zk通知,而是直接給broker發送rpc請求。

如果ISR列表是空,那麼會根據配置,隨便選一個replica做leader,或者乾脆這個partition就是歇菜。如果ISR列表的有機器,但是也歇菜了,那麼還可以等ISR的機器活過來。

2.2 多副本同步

這裏的策略,服務端這邊的處理是follower從leader批量拉取數據來同步。但是具體的可靠性,是由生產者來決定的。

生產者生產消息的時候,通過request.required.acks參數來設置數據的可靠性。

acks what happen
0 which means that the producer never waits for an acknowledgement from the broker.發過去就完事了,不關心broker是否處理成功,可能丟數據。
1 which means that the producer gets an acknowledgement after the leader replica has received the data. 當寫Leader成功後就返回,其他的replica都是通過fetcher去同步的,所以kafka是異步寫,主備切換可能丟數據。
-1 which means that the producer gets an acknowledgement after all in-sync replicas have received the data. 要等到isr裏所有機器同步成功,才能返回成功,延時取決於最慢的機器。強一致,不會丟數據。

在acks=-1的時候,如果ISR少於min.insync.replicas指定的數目,那麼就會返回不可用。

這裏ISR列表中的機器是會變化的,根據配置 replica.lag.time.max.ms,多久沒同步,就會從ISR列表中剔除。以前還有根據落後多少條消息就踢出ISR,在1.0版本後就去掉了,因爲這個值很難取,在高峯的時候很容易出現節點不斷的進出ISR列表。

從ISA中選出leader後,follower會從把自己日誌中上一個高水位後面的記錄去掉,然後去和leader拿新的數據。因爲新的leader選出來後,follower上面的數據,可能比新leader多,所以要截取。這裏高水位的意思,對於partition和leader,就是所有ISR中都有的最新一條記錄。消費者最多隻能讀到高水位;

從leader的角度來說高水位的更新會延遲一輪,例如寫入了一條新消息,ISR中的broker都fetch到了,但是ISR中的broker只有在下一輪的fetch中才能告訴leader。

也正是由於這個高水位延遲一輪,在一些情況下,kafka會出現丟數據和主備數據不一致的情況,0.11開始,使用leader epoch來代替高水位。

3. 消費

訂閱topic是以一個消費組來訂閱的,一個消費組裏面可以有多個消費者。同一個消費組中的兩個消費者,不會同時消費一個partition。換句話來說,就是一個partition,只能被消費組裏的一個消費者消費,但是可以同時被多個消費組消費。因此,如果消費組內的消費者如果比partition多的話,那麼就會有個別消費者一直空閒。

3.1 offset保存

一個消費組消費partition,需要保存offset記錄消費到哪,以前保存在zk中,由於zk的寫性能不好,以前的解決方法都是consumer每隔一分鐘上報一次。這裏zk的性能嚴重影響了消費的速度,而且很容易出現重複消費。

0.10版本後,kafka把這個offset的保存,從zk總剝離,保存在一個名叫__consumeroffsets topic的topic中。寫進消息的key由groupid、topic、partition組成,value是偏移量offset。topic配置的清理策略是compact。總是保留最新的key,其餘刪掉。一般情況下,每個key的offset都是緩存在內存中,查詢的時候不用遍歷partition,如果沒有緩存,第一次就會遍歷partition建立緩存,然後查詢返回。

確定consumer group位移信息寫入__consumers_offsets的哪個partition,具體計算公式:

__consumers_offsets partition =
	           Math.abs(groupId.hashCode() % groupMetadataTopicPartitionCount)   
	//groupMetadataTopicPartitionCount由offsets.topic.num.partitions指定,默認是50個分區。

3.2 分配partition–reblance

生產過程中broker要分配partition,消費過程這裏,也要分配partition給消費者。類似broker中選了一個controller出來,消費也要從broker中選一個coordinator,用於分配partition。

下面從頂向下,分別闡述一下

  1. 怎麼選coordinator
  2. 交互流程
  3. reblance的流程

3.2.1 選coordinator

  1. 看offset保存在那個partition
  2. 該partition leader所在的broker就是被選定的coordinator

這裏我們可以看到,consumer group的coordinator,和保存consumer group offset的partition leader是同一臺機器。

3.2.2 交互流程

把coordinator選出來之後,就是要分配了
整個流程是這樣的:

  1. consumer啓動、或者coordinator宕機了,consumer會任意請求一個broker,發送ConsumerMetadataRequest請求,broker會按照上面說的方法,選出這個consumer對應coordinator的地址。
  2. consumer 發送heartbeat請求給coordinator,返回IllegalGeneration的話,就說明consumer的信息是舊的了,需要重新加入進來,進行reblance。返回成功,那麼consumer就從上次分配的partition中繼續執行。

3.2.3 reblance流程

Rebalance 發生時,Group 下所有 consumer 實例都會協調在一起共同參與,kafka 能夠保證儘量達到最公平的分配。但是 Rebalance 過程對 consumer group 會造成比較嚴重的影響。在 Rebalance 的過程中 consumer group 下的所有消費者實例都會停止工作,等待 Rebalance 過程完成。

列舉一下會reblance的情況:

  • 增加partition
  • 增加消費者
  • 消費者主動關閉
  • 消費者宕機
  • coordinator自己也宕機

Rebalance 過程分爲兩步:Join 和 Sync。

Join 顧名思義就是加入組。這一步中,所有成員都向coordinator發送JoinGroup請求,請求加入消費組。一旦所有成員都發送了JoinGroup請求,coordinator會從中選擇一個consumer擔任leader的角色,並把組成員信息以及訂閱信息發給leader——注意leader和coordinator不是一個概念。leader負責消費分配方案的制定。

Sync,這一步leader開始分配消費方案,即哪個consumer負責消費哪些topic的哪些partition。一旦完成分配,leader會將這個方案封裝進SyncGroup請求中發給coordinator,非leader也會發SyncGroup請求,只是內容爲空。coordinator接收到分配方案之後會把方案塞進SyncGroup的response中發給各個consumer。這樣組內的所有成員就都知道自己應該消費哪些分區了。

綜合來講:

  1. consumer給coordinator發送JoinGroupRequest請求。

  2. 這時其他consumer發heartbeat請求過來時,coordinator會告訴他們,要reblance了。

  3. 其他consumer發送JoinGroupRequest請求。

  4. 所有記錄在冊的consumer都發了JoinGroupRequest請求之後,coordinator就會在這裏consumer中隨便選一個leader。然後回JoinGroupRespone,這會告訴consumer你是follower還是leader,對於leader,還會把follower的信息帶給它,讓它根據這些信息去分配partition

  5. consumer向coordinator發送SyncGroupRequest,其中leader的SyncGroupRequest會包含分配的情況。

  6. coordinator回包,把分配的情況告訴consumer,包括leader。
    當partition或者消費者的數量發生變化時,都得進行reblance。

4. 消息投遞語義

kafka支持3種消息投遞語義

  • At most once:最多一次,消息可能會丟失,但不會重複
  • At least once:最少一次,消息不會丟失,可能會重複
  • Exactly once:只且一次,消息不丟失不重複,只且消費一次(0.11中實現,僅限於下游也是kafka)

在業務中,常常都是使用At least once的模型,如果需要可重入的話,往往是業務自己實現。

4.1 At least once

先獲取數據,再進行業務處理,業務處理成功後commit offset。

  1. 生產者生產消息異常,消息是否成功寫入不確定,重做,可能寫入重複的消息
  2. 消費者處理消息,業務處理成功後,更新offset失敗,消費者重啓的話,會重複消費

4.2 At most once

先獲取數據,再commit offset,最後進行業務處理。

  1. 生產者生產消息異常,不管,生產下一個消息,消息就丟了
  2. 消費者處理消息,先更新offset,再做業務處理,做業務處理失敗,消費者重啓,消息就丟了

4.3 Exactly once

思路是這樣的,首先要保證消息不丟,再去保證不重複。所以盯着At least once的原因來搞。 首先想出來的:

生產者重做導致重複寫入消息----生產保證冪等性
消費者重複消費—消滅重複消費,或者業務接口保證冪等性重複消費也沒問題

由於業務接口是否冪等,不是kafka能保證的,所以kafka這裏提供的exactly once是有限制的,消費者的下游也必須是kafka。所以一下討論的,沒特殊說明,消費者的下游系統都是kafka(注:使用kafka conector,它對部分系統做了適配,實現了exactly once)。
生產者冪等性好做,沒啥問題。
解決重複消費有兩個方法:

  1. 下游系統保證冪等性,重複消費也不會導致多條記錄。
  2. 把commit offset和業務處理綁定成一個事務。

本來exactly once實現第1點就ok了。
但是在一些使用場景下,我們的數據源可能是多個topic,處理後輸出到多個topic,這時我們會希望輸出時要麼全部成功,要麼全部失敗。這就需要實現事務性。既然要做事務,那麼幹脆把重複消費的問題從根源上解決,把commit offset和輸出到其他topic綁定成一個事務。

Ref

  1. https://www.jianshu.com/p/d3e963ff8b70
  2. https://www.cnblogs.com/yoke/p/11405397.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章