GroupCoodinator 和KafkaController一样,在每一个broker都会启动一个GroupCoodinator,Kafka 按照消费者组的名称将其分配给对应的GroupCoodinator进行管理;每一个GroupCoodinator只负责管理一部分消费者组,而非集群中全部的消费者组。
一 功能
# 记录消费者组的信息
# 通过心跳消息检测消费者状态
# 针对JoinGroupRequest和SyncGroupRequest进行消费者组中分区的分配
# 维护消费的offset信息
二 MemberMetadata
GroupCoodinator使用MemberMetadata记录消费者的元数据,包括以下字段:
memberId: String 对应消费者的id
groupId: String 消费者对应的消费者组的id
rebalanceTimeoutMs: Int rebalance操作超时时间
sessionTimeoutMs: Int 心跳超时时间
assignment:Array[Byte] 记录分配给当前消费者的分区消息
supportedProtocols: List[(String Array[Byte])] 消费者支持的PartitionAssignor
latestHeartbeat: Long 最后一次心跳消息的时间戳
isLeaving: Boolean 对应消费者是否已经离开消费者组了
三 GroupMetadata
GroupMetadata记录了消费者组的元数据信息,其字段含义如下:
groupId: String 消费者组对应的id
state: GroupState 消费者组当前所处的状态
members: HashMap[String, MemberMetadata] 保存了消费者和对应的消费者元数据信息
offsets: HashMap[TopicPartition,OffsetAndMetadata] 保存了分区和其对应的offset映射关系
generationId: Int 消费者组的年代信息
leaderId: String 消费者组中的leader信息
protocol: String 记录了当前消费者组选择的PartitionAssignor
它提供了一些方法,比如针对members的增删,对state的切换。在进行Member增删的时候,顺便会选择消费者组里leader
def add(memberId: String, member: MemberMetadata) {
if (members.isEmpty)
this.protocolType = Some(member.protocolType)
assert(groupId == member.groupId)
assert(this.protocolType.orNull == member.protocolType)
assert(supportsProtocols(member.protocols))
if (leaderId == null)
leaderId = memberId
members.put(memberId, member)
}
def remove(memberId: String) {
members.remove(memberId)
if (memberId == leaderId) {
leaderId = if (members.isEmpty) {
null
} else {
members.keys.head
}
}
}
为消费者组选择合适的PartitionAssignor功能:
def selectProtocol
: String = {
if (members.isEmpty)
throw new IllegalStateException("Cannot select protocol for empty group")
// 所有消费者都支持的协议作为候选协议
val candidates = candidateProtocols
// 每一个消费者都会通过vote方法进行投票,会为其supportedProtocols集合里的第一个PartitionAssignor投一票,最终将选择的票最多的PartitionAssignor
val votes: List[(String, Int)] = allMemberMetadata
.map(_.vote(candidates))
.groupBy(identity)
.mapValues(_.size)
.toList
votes.maxBy(_._2)._1
}