Kafka 消费者组管理模块(四):__consumer_offsets中保存的数据类型

        __consumer_offsets 中的注册消息(Group Metadata)和位移消息(Offset Commit)。
        位移主题有两类消息:消费者组注册消息(Group Metadata)和消费者组的已提交位移消息(Offset Commit)。

注册消息

        所谓的注册消息,就是指消费者组向位移主题写入注册类的消息。所有成员都加入组后:Coordinator 向位移主题写入注册消息,只是该消息不含分区消费分配方案;Leader 成员发送方案给 Coordinator 后:当 Leader 成员将分区消费分配方案发给 Coordinator 后,Coordinator 写入携带分配方案的注册消息。
        所有成员都加入组后:Coordinator 向位移主题写入注册消息,只是该消息不含分区消费分配方案;
        Leader 成员发送方案给 Coordinator 后:当 Leader 成员将分区消费分配方案发给 Coordinator 后,Coordinator 写入携带分配方案的注册消息。.

注册消息的 Key 

case class GroupMetadataKey(version: Short, key: String) extends BaseKey {

  override def toString: String = key
}

        该类的 key 字段是一个字符串类型,保存的是消费者组的名称。注册消息的 Key 就是消费者组名。
        GroupMetadataManager 对象有个 groupMetadataKey 方法,负责将注册消息的 Key 转换成字节数组,用于后面构造注册消息。

  def groupMetadataKey(group: String): Array[Byte] = {
    val key = new Struct(CURRENT_GROUP_KEY_SCHEMA)
    key.set(GROUP_KEY_GROUP_FIELD, group)
    // 构造一个ByteBuffer对象,容纳version和key数据
    val byteBuffer = ByteBuffer.allocate(2 /* version */ + key.sizeOf)
    byteBuffer.putShort(CURRENT_GROUP_KEY_SCHEMA_VERSION)
    // 依次向 Buffer 写入 Short 型的消息格式版本以及消费者组名
    key.writeTo(byteBuffer)
    byteBuffer.array()
  }

消息体 Value 

        groupMetadataValue 方法将消费者组重要的元数据写入到字节数组。

  def groupMetadataValue(groupMetadata: GroupMetadata, // 消费者组元数据对象
                         assignment: Map[String, Array[Byte]], // 分区消费分配方案
                         apiVersion: ApiVersion): Array[Byte] = { // Kafka API版本号
    // 确定消息格式版本以及格式结构
    val (version, value) = {
      if (apiVersion < KAFKA_0_10_1_IV0)
        (0.toShort, new Struct(GROUP_METADATA_VALUE_SCHEMA_V0))
      else if (apiVersion < KAFKA_2_1_IV0)
        (1.toShort, new Struct(GROUP_METADATA_VALUE_SCHEMA_V1))
      else if (apiVersion < KAFKA_2_3_IV0)
        (2.toShort, new Struct(GROUP_METADATA_VALUE_SCHEMA_V2))
      else
        (3.toShort, new Struct(GROUP_METADATA_VALUE_SCHEMA_V3))
    }
    // 依次写入消费者组主要的元数据信息
    // 包括协议类型、Generation ID、分区分配策略和Leader成员ID
    value.set(PROTOCOL_TYPE_KEY, groupMetadata.protocolType.getOrElse(""))
    value.set(GENERATION_KEY, groupMetadata.generationId)
    value.set(PROTOCOL_KEY, groupMetadata.protocolOrNull)
    value.set(LEADER_KEY, groupMetadata.leaderOrNull)

    if (version >= 2)
      value.set(CURRENT_STATE_TIMESTAMP_KEY, groupMetadata.currentStateTimestampOrDefault)
    // 写入各个成员的元数据信息
    // 包括成员ID、client.id、主机名以及会话超时时间
    val memberArray = groupMetadata.allMemberMetadata.map { memberMetadata =>
      val memberStruct = value.instance(MEMBERS_KEY)
      memberStruct.set(MEMBER_ID_KEY, memberMetadata.memberId)
      memberStruct.set(CLIENT_ID_KEY, memberMetadata.clientId)
      memberStruct.set(CLIENT_HOST_KEY, memberMetadata.clientHost)
      memberStruct.set(SESSION_TIMEOUT_KEY, memberMetadata.sessionTimeoutMs)
      // 写入Rebalance超时时间
      if (version > 0)
        memberStruct.set(REBALANCE_TIMEOUT_KEY, memberMetadata.rebalanceTimeoutMs)
      // 写入用于静态消费者组管理的Group Instance ID
      if (version >= 3)
        memberStruct.set(GROUP_INSTANCE_ID_KEY, memberMetadata.groupInstanceId.orNull)

      // The group is non-empty, so the current protocol must be defined
      // 必须定义分区分配策略,否则抛出异常
      val protocol = groupMetadata.protocolOrNull
      if (protocol == null)
        throw new IllegalStateException("Attempted to write non-empty group metadata with no defined protocol")
      // 写入成员消费订阅信息
      val metadata = memberMetadata.metadata(protocol)
      memberStruct.set(SUBSCRIPTION_KEY, ByteBuffer.wrap(metadata))

      val memberAssignment = assignment(memberMetadata.memberId)
      assert(memberAssignment != null)
      // 写入成员消费分配信息
      memberStruct.set(ASSIGNMENT_KEY, ByteBuffer.wrap(memberAssignment))

      memberStruct
    }

    value.set(MEMBERS_KEY, memberArray.toArray)
    // 向Buffer依次写入版本信息和以上写入的元数据信息
    val byteBuffer = ByteBuffer.allocate(2 /* version */ + value.sizeOf)
    byteBuffer.putShort(version)
    value.writeTo(byteBuffer)
    // 返回Buffer底层的字节数组
    byteBuffer.array()
  }

已提交位移消息

        提交位移消息的 Key 和 Value 构成。Key 是一个 GroupTopicPartition 类型,也就是 < 消费者组名,主题,分区号 > 三元组。

提交位移消息的 Key 定义

case class OffsetKey(version: Short, key: GroupTopicPartition) extends BaseKey {

  override def toString: String = key.toString
}

        offsetCommitKey 方法负责将这个三元组转换成字节数组,用于后续构造提交位移消息。

  def offsetCommitKey(group: String,   // 消费者组名
                      topicPartition: TopicPartition): Array[Byte] = { // 主题 + 分区号
    // 创建结构体,依次写入消费者组名、主题和分区号
    val key = new Struct(CURRENT_OFFSET_KEY_SCHEMA)
    key.set(OFFSET_KEY_GROUP_FIELD, group)
    key.set(OFFSET_KEY_TOPIC_FIELD, topicPartition.topic)
    key.set(OFFSET_KEY_PARTITION_FIELD, topicPartition.partition)
    // 构造ByteBuffer,写入格式版本和结构体
    val byteBuffer = ByteBuffer.allocate(2 /* version */ + key.sizeOf)
    byteBuffer.putShort(CURRENT_OFFSET_KEY_SCHEMA_VERSION)
    key.writeTo(byteBuffer)
    // 返回字节数组
    byteBuffer.array()
  }

提交位移消息的 value

        offsetCommitValue 方法决定了 Value 中都有哪些元素

  def offsetCommitValue(offsetAndMetadata: OffsetAndMetadata,
                        apiVersion: ApiVersion): Array[Byte] = {
    // generate commit value according to schema version
    // 确定消息格式版本以及创建对应的结构体对象
    val (version, value) = {
      if (apiVersion < KAFKA_2_1_IV0 || offsetAndMetadata.expireTimestamp.nonEmpty) {
        val value = new Struct(OFFSET_COMMIT_VALUE_SCHEMA_V1)
        value.set(OFFSET_VALUE_OFFSET_FIELD_V1, offsetAndMetadata.offset)
        value.set(OFFSET_VALUE_METADATA_FIELD_V1, offsetAndMetadata.metadata)
        value.set(OFFSET_VALUE_COMMIT_TIMESTAMP_FIELD_V1, offsetAndMetadata.commitTimestamp)
        // version 1 has a non empty expireTimestamp field
        value.set(OFFSET_VALUE_EXPIRE_TIMESTAMP_FIELD_V1,
          offsetAndMetadata.expireTimestamp.getOrElse(OffsetCommitRequest.DEFAULT_TIMESTAMP))
        (1, value)
      } else if (apiVersion < KAFKA_2_1_IV1) {
        val value = new Struct(OFFSET_COMMIT_VALUE_SCHEMA_V2)
        // 依次写入位移值、Leader Epoch值、自定义元数据以及时间戳
        value.set(OFFSET_VALUE_OFFSET_FIELD_V2, offsetAndMetadata.offset)
        value.set(OFFSET_VALUE_METADATA_FIELD_V2, offsetAndMetadata.metadata)
        value.set(OFFSET_VALUE_COMMIT_TIMESTAMP_FIELD_V2, offsetAndMetadata.commitTimestamp)
        (2, value)
      } else {
        val value = new Struct(OFFSET_COMMIT_VALUE_SCHEMA_V3)
        value.set(OFFSET_VALUE_OFFSET_FIELD_V3, offsetAndMetadata.offset)
        value.set(OFFSET_VALUE_LEADER_EPOCH_FIELD_V3,
          offsetAndMetadata.leaderEpoch.orElse(RecordBatch.NO_PARTITION_LEADER_EPOCH))
        value.set(OFFSET_VALUE_METADATA_FIELD_V3, offsetAndMetadata.metadata)
        value.set(OFFSET_VALUE_COMMIT_TIMESTAMP_FIELD_V3, offsetAndMetadata.commitTimestamp)
        (3, value)
      }
    }
    // 构建ByteBuffer,写入消息格式版本和结构体
    val byteBuffer = ByteBuffer.allocate(2 /* version */ + value.sizeOf)
    byteBuffer.putShort(version.toShort)
    value.writeTo(byteBuffer)
    // 返回ByteBuffer底层字节数组
    byteBuffer.array()
  }

Tombstone 消息

        Kafka 源码中还存在一类消息,那就是 Tombstone 消息。它就是 Value 为 null 的消息。这个消息的主要作用,是让 Kafka 识别哪些 Key 对应的消息是可以被删除的,有了它,Kafka 就能保证,内部位移主题不会持续增加磁盘占用空间。

  • 一旦注册消息中出现了 Tombstone 消息,就表示 Kafka 可以将该消费者组元数据从位移主题中删除;
  • 一旦提交位移消息中出现了 Tombstone,就表示 Kafka 能够将该消费者组在某主题分区上的位移提交数据删除。
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值