消费者组的元数据主要是由 GroupMetadata 和 MemberMetadata 两个类组成,它们分别位于 GroupMetadata.scala 和 MemberMetadata.scala 这两个源码文件中。从它们的名字上也可以看出来,前者是保存消费者组的元数据,后者是保存消费者组下成员的元数据。
MemberMetadata
先看 MemberMetadata.scala 文件,包括 3 个类和对象
- MemberSummary 类:组成员概要数据,提取了最核心的元数据信息。
- MemberMetadata 伴生对象:仅仅定义了一个工具方法,供上层组件调用。
- MemberMetadata 类:消费者组成员的元数据。Kafka 为消费者组成员定义了很多数据,一会儿我们将会详细学习。
MemberSummary 类
组成员元数据的一个概要数据类
case class MemberSummary( memberId: String, // 成员ID,由Kafka自动生成
groupInstanceId: Option[String], // Consumer端参数group.instance.id值,消费者组静态成员的 ID。
clientId: String, // client.id参数值
clientHost: String, // Consumer端程序主机名
metadata: Array[Byte], // 消费者组成员使用的分配策略
assignment: Array[Byte]) // 成员订阅分区)
MemberMetadata 伴生对象
它只定义了一个 plainProtocolSet 方法,供上层组件调用。这个方法只做一件事儿,即从一组给定的分区分配策略详情中提取出分区分配策略的名称,并将其封装成一个集合对象。
private object MemberMetadata {
// 提取分区分配策略集合
// 如果消费者组下有 3 个成员,它们的 partition.assignment.strategy 参数分别设置成 RangeAssignor、RangeAssignor 和 RoundRobinAssignor,那么,plainProtocolSet 方法的返回值就是集合[RangeAssignor,RoundRobinAssignor]。
def plainProtocolSet(supportedProtocols: List[(String, Array[Byte])]) = supportedProtocols.map(_._1).toSet
}
MemberMetadata 类
private[group] class MemberMetadata(var memberId: String,
val groupId: String,
val groupInstanceId: Option[String],
val clientId: String,
val clientHost: String,
val rebalanceTimeoutMs: Int, // Rebalane操作超时时间,即一次 Rebalance 操作必须在这个时间内完成,否则被视为超时。
val sessionTimeoutMs: Int, // 会话超时时间,当前消费者组成员依靠心跳机制“保活”。如果在会话超时时间之内未能成功发送心跳,组成员就被判定成“下线”,从而触发新一轮的 Rebalance。
val protocolType: String, // 对消费者组而言,是"consumer",还有"connect"
var supportedProtocols: List[(String, Array[Byte])] // 分区分配方案
) {
var assignment: Array[Byte] = Array.empty[Byte] // 保存分配给该成员的分区分配方案。
var awaitingJoinCallback: JoinGroupResult => Unit = null // 表示组成员是否正在等待加入组。
var awaitingSyncCallback: SyncGroupResult => Unit = null // 表示组成员是否正在等待 GroupCoordinator 发送分配方案。
var isLeaving: Boolean = false // 表示组成员是否发起“退出组”的操作。
var isNew: Boolean = false // 表示是否是消费者组下的新成员。
val isStaticMember: Boolean = groupInstanceId.isDefined
// This variable is used to track heartbeat completion through the delayed
// heartbeat purgatory. When scheduling a new heartbeat expiration, we set
// this value to `false`. Upon receiving the heartbeat (or any other event
// indicating the liveness of the client), we set it to `true` so that the
// delayed heartbeat can be completed.
var heartbeatSatisfied: Boolean = false
def isAwaitingJoin = awaitingJoinCallback != null
def isAwaitingSync = awaitingSyncCallback != null
}
其中的metadata方法从该成员配置的分区分配方案列表中寻找给定策略的详情。
def metadata(protocol: String): Array[Byte] = {
// 从配置的分区分配策略中寻找给定策略
supportedProtocols.find(_._1 == protocol) match {
case Some((_, metadata)) => metadata
case None =>
throw new IllegalArgumentException("Member does not support protocol")
}
}
组元数据(GroupMetadata 类)
private[group] class GroupMetadata(val groupId: String, // 组ID
initialState: GroupState, // 消费者组初始状态
time: Time) extends Logging {
type JoinCallback = JoinGroupResult => Unit
private[group] val lock = new ReentrantLock
// 定义了消费者组的状态空间。当前有 5 个状态,
// Empty 表示当前无成员的消费者组;
// PreparingRebalance 表示正在执行加入组操作的消费者组;
// CompletingRebalance 表示等待 Leader 成员制定分配方案的消费者组;
// Stable 表示已完成 Rebalance 操作可正常工作的消费者组;
// Dead 表示当前无成员且元数据信息被删除的消费者组。
private var state: GroupState = initialState
// 记录状态最近一次变更的时间戳,用于确定位移主题中的过期消息。
// 位移主题中的消息也要遵循 Kafka 的留存策略,所有当前时间与该字段的差值超过了留存阈值的消息都被视为“已过期”(Expired)。
var currentStateTimestamp: Option[Long] = Some(time.milliseconds())
var protocolType: Option[String] = None
// 消费组 Generation 号。Generation 等同于消费者组执行过 Rebalance 操作的次数,每次执行 Rebalance 时,Generation 数都要加 1。
var generationId = 0
// 记录消费者组的Leader成员,可能不存在
// 消费者组中 Leader 成员的 Member ID 信息。当消费者组执行 Rebalance 过程时,需要选举一个成员作为 Leader,负责为所有成员制定分区分配方案。
// 在 Rebalance 早期阶段,这个 Leader 可能尚未被选举出来。这就是,leaderId 字段是 Option 类型的原因。
private var leaderId: Option[String] = None
private var protocol: Option[String] = None
// 成员元数据列表信息
private val members = new mutable.HashMap[String, MemberMetadata]
// Static membership mapping [key: group.instance.id, value: member.id]
// 静态成员Id列表
private val staticMembers = new mutable.HashMap[String, String]
private val pendingMembers = new mutable.HashSet[String]
private var numMembersAwaitingJoin = 0
// 分区分配策略支持票数
private val supportedProtocols = new mutable.HashMap[String, Integer]().withDefaultValue(0)
// 保存消费者组订阅分区的提交位移值
private val offsets = new mutable.HashMap[TopicPartition, CommitRecordMetadataAndOffset]
private val pendingOffsetCommits = new mutable.HashMap[TopicPartition, OffsetAndMetadata]
private val pendingTransactionalOffsetCommits = new mutable.HashMap[Long, mutable.Map[TopicPartition, CommitRecordMetadataAndOffset]]()
private var receivedTransactionalOffsetCommits = false
private var receivedConsumerOffsetCommits = false
// When protocolType == `consumer`, a set of subscribed topics is maintained. The set is
// computed when a new generation is created or when the group is restored from the log.
// 消费者组订阅的主题列表
private var subscribedTopics: Option[Set[String]] = None
var newMemberAdded: Boolean = false
}