- Kafka的消息消費是以消費的group為單位;
- 同屬一個group中的多個consumer分別消費topic的不同partition;
- 同組內(nèi)consumer的變化, partition變化, coordinator的變化都會引發(fā)balance;
- 消費的offset的提交
- Kafka wiki: Kafka Detailed Consumer Coordinator Design 和 Kafka Client-side Assignment Proposal
GroupMetadata類
- 所在文件:
core/src/main/scala/kafka/coordinator/MemberMetadata.scala
- 作用: 用來表示一個消費group的相關(guān)信息
- 當前group的狀態(tài):
private var state: GroupState = Stable
- Stable: consumer group的balance已完成, 處于穩(wěn)定狀態(tài);
- PreparingRebalance: 收到JoinRequest, consumer group需要重新作balance時的狀態(tài);
-
AwaitingSync: 收到了所有需要的JoonRequest, 等待作為當前group的leader的consumer客戶端提交balance的結(jié)果到
coordinator
; - Dead: 當前的消費group不再有任何consumer成員時的狀態(tài);
- 當前group的成員相關(guān)信息:
- 成員信息:
private val members = new mutable.HashMap[String, MemberMetadata]
,
每個成員都有一個memberId, 對應(yīng)著MemberMetadata
; -
var leaderId: String
: 對于group的balance, 簡單來講實際上是Coordinator
收集了所有的consumer的信息后, 將其發(fā)送給group中的一個consumer, 這個consumer負責(zé)按一定的balance策略,將partition分配到不同的consumer, 這個分配結(jié)果會Sync回Coordinator
, 然后再同步到各個consumer, 這個負責(zé)具體分配的consumer就是當前的Leader
; 這個Leader
的決定很簡單, 誰第一個加入這個group的,誰就是leader; -
var protocol: String
: 當前group組所采用的balance策略, 選取的規(guī)則是被當前所有member都支持的策略中最多的那一個; -
var generationId
: 當前balance的一個標識id, 可以簡單理解成是第幾次作balance, 每次狀態(tài)轉(zhuǎn)換到AwaitingSync
時, 其值就增加1;
- 成員信息:
GroupMetadataManager類
- 所在文件:
core/src/main/scala/kafka/coordinator/GroupMetadataManager.scala
- 作用: 是比較核心的一個類, 負責(zé)所有g(shù)roup的管理, offset消息的讀寫和清理等, 下面我們一一道來
-
當前所有消費group的管理:
-
private val groupsCache = new Pool[String, GroupMetadata]
: 緩存了所有GroupMetadata
的信息; - 針對
groupsCache
的管理接口:
-
def getGroup(groupId: String): GroupMetadata
def addGroup(group: GroupMetadata): GroupMetadata
def removeGroup(group: GroupMetadata)
-
__consumer_offsets topic的讀寫
- 我們已經(jīng)知道現(xiàn)在的kafka已經(jīng)支持將offset信息保存到broker上, 實際上是保存到一個內(nèi)部的topic上:
__consumer_offsets
, 寫入其中的msg都包含有key
-
__consumer_offsets
這個topic里實際上保存兩種類型消息:
2.1 一部分是offset信息(kafka.coordinator.OffsetsMessageFormatter
類型)的:
[groupId,topic,partition]::[OffsetMetadata[offset,metadata],CommitTime ExprirationTime]
, 它的key
是[groupId,topic,partition]
2.2 另一部分是group信息(kafka.coordinator.GroupMetadataMessageFormatter
類型):
groupId::[groupId,Some(consumer),groupState,Map(memberId -> [memberId,clientId,clientHost,sessionTimeoutMs], ...->[]...)]
, 這部分實際上就是把當前Stable
狀態(tài)的GroupMetadata
存到了__consumer_offsets
里, , 它的key
是groupId
- offset和group信息的寫入: 實際上是普通的消息寫入沒有本質(zhì)上的區(qū)別, 可參考Kafka是如何處理客戶端發(fā)送的數(shù)據(jù)的?, 這里的方法是
def store(delayedAppend: DelayedStore)
, 實現(xiàn)就是調(diào)用replicaManager.appendMessages
來寫入消息到log文件
- 我們已經(jīng)知道現(xiàn)在的kafka已經(jīng)支持將offset信息保存到broker上, 實際上是保存到一個內(nèi)部的topic上:
-
__consumer_offsets
topic消息的加載-
__consumer_offsets
作為一個topic, 也是有多個partiton的, 每個partiton也是有多個復(fù)本的, partition也會經(jīng)歷leader的選舉,也會有故障轉(zhuǎn)移操作; - 當
__consumer_offsets
在某臺broker上的partition成為leader partition
時, 需要先從本地的log文件后加載offset,group相關(guān)信息到內(nèi)存, 加載完成后才能對外提供讀寫和balance的操作; - 具體實現(xiàn):
def loadGroupsForPartition(offsetsPartition: Int, onGroupLoaded: GroupMetadata => Unit)
-
-
offset的相關(guān)操作
- 使用者消費msg提交的offset, 不僅會寫入到log文件后, 為了快速響應(yīng)還會緩存在內(nèi)存中, 對應(yīng)
private val offsetsCache = new Pool[GroupTopicPartition, OffsetAndMetadata]
; - 直接從內(nèi)存中獲取某一group對應(yīng)某一topic的parition的offset信息:
def getOffsets(group: String, topicPartitions: Seq[TopicAndPartition]): Map[TopicAndPartition, OffsetMetadataAndError]
- 刷新offset:
offsetsCache
只保存最后一次提交的offset信息
private def putOffset(key: GroupTopicPartition, offsetAndMetadata: OffsetAndMetadata)
- 使用者消費msg提交的offset, 不僅會寫入到log文件后, 為了快速響應(yīng)還會緩存在內(nèi)存中, 對應(yīng)
-
刪除過期的offset消息
-
GroupMetadataManager
在啟動時會同時啟動一個名為delete-expired-consumer-offsets
定時任務(wù)來定時刪除過期的offset信息; - 從內(nèi)存緩存中清除:
offsetsCache.remove(groupTopicAndPartition)
- 從已經(jīng)落地的log文件中清除: 實現(xiàn)就是向log里寫一條payload為null的"墓碑"message作為標記,
__consumer_offsets
的清除策略默認是compact
, 后面我們會單獨開一章來講日志的清除;
-
GroupCoordinator類
- 所在文件:
core/src/main/scala/kafka/coordinator/GroupCoordinator.scala
- 核心類, 處理所有和消息消費相關(guān)的request:
case RequestKeys.OffsetCommitKey => handleOffsetCommitRequest(request)
case RequestKeys.OffsetFetchKey => handleOffsetFetchRequest(request)
case RequestKeys.GroupCoordinatorKey => handleGroupCoordinatorRequest(request)
case RequestKeys.JoinGroupKey => handleJoinGroupRequest(request)
case RequestKeys.HeartbeatKey => handleHeartbeatRequest(request)
case RequestKeys.LeaveGroupKey => handleLeaveGroupRequest(request)
case RequestKeys.SyncGroupKey => handleSyncGroupRequest(request)
case RequestKeys.DescribeGroupsKey => handleDescribeGroupRequest(request)
case RequestKeys.ListGroupsKey => handleListGroupsRequest(request)
- 使用簡單狀態(tài)機來協(xié)調(diào)consumer group的balance;
- 下面我們假設(shè)在一個group:g1中啟動兩個consumer: c1和c2來消費同一個topic, 來看看狀態(tài)機的轉(zhuǎn)換
- 第一種情況: c1和c2分別啟動:
c2.jpg
- 第二種情況: c1和c2已經(jīng)在group中, 然后c1正常的退出離開
c1.jpg
- 第二種情況: c1和c2已經(jīng)在group中, 然后c1非正常退出,比如說進程被kill掉
流程跟上面的2基本上一致, 只不過(1)這步的觸發(fā)條件不是LeaveGroupRequest, 而是來自c1的heartbeat的onExpireHeartbeat; - 第四種情況: c1和c2已經(jīng)在group中, 然后這個topic的partition增加, 這個時候服務(wù)端是無法主動觸發(fā)的,客戶端會定時去服務(wù)端同步metadata信息, 從新的metadata信息中客戶端會獲知partition有了變化, 此時c1和c2會重新發(fā)送
JoinRequest
來觸發(fā)新的balance; - 還有其它的兩種情況, 這里就不一一說明了,總之就是利用這個狀態(tài)機的轉(zhuǎn)換來作相應(yīng)的處理.