Skip to content

Consumer Group 协议

Consumer Group 是 Kafka 实现水平扩展消费的核心机制。它让多个 Consumer 协作消费同一批 Partition,并在成员变化时自动重新分配。

基本约束

Topic orders: P0, P1, P2

Group order-service
├── Consumer A -> P0
├── Consumer B -> P1
└── Consumer C -> P2

同一个 Consumer Group 内:

  • 一个 Partition 同一时间只能分配给一个 Consumer
  • 一个 Consumer 可以消费多个 Partition
  • Consumer 数量大于 Partition 数量时,多出来的 Consumer 会空闲
  • 不同 Consumer Group 之间互不影响,可以独立消费同一 Topic

Coordinator

每个 Consumer Group 都由一个 Group Coordinator 管理。Coordinator 是某个 Broker,它负责:

职责说明
管理成员处理 JoinGroup、LeaveGroup、心跳
触发 Rebalance成员变化或订阅变化时重新分配
保存 OffsetOffset 写入内部 Topic __consumer_offsets
检测故障根据心跳和超时判断 Consumer 是否存活

Consumer 如何找到 Coordinator:

group.id

  ├── hash(group.id)
  ├── 定位 __consumer_offsets 的某个 Partition
  └── 该 Partition 的 Leader Broker 就是 Coordinator

JoinGroup 与 SyncGroup

Consumer 加入 Group 时大致流程:

Consumer A/B/C

  ├── FindCoordinator
  ├── JoinGroup
  │     └── Coordinator 选出 Group Leader
  ├── Group Leader 执行分区分配
  ├── SyncGroup 分发分配结果
  └── 开始 poll 消费

这里的 Group Leader 不是 Broker Leader,而是 Consumer Group 内被选中的一个 Consumer。它负责根据分配策略生成 Partition 分配方案。

心跳与超时

Consumer 需要持续向 Coordinator 发送心跳。

配置说明
heartbeat.interval.ms心跳发送间隔
session.timeout.msCoordinator 多久没收到心跳就认为 Consumer 失效
max.poll.interval.msConsumer 两次 poll 的最大间隔

常见问题:

  • 业务处理太慢,超过 max.poll.interval.ms,Consumer 被踢出 Group
  • GC 或网络抖动导致心跳超时,触发 Rebalance
  • Consumer 实例频繁扩缩容,导致 Group 一直不稳定

Rebalance 触发条件

触发条件示例
Consumer 加入扩容一个消费实例
Consumer 离开实例关闭、崩溃、心跳超时
Topic Partition 变化Topic 扩分区
订阅 Topic 变化正则订阅匹配到新 Topic
Coordinator 迁移Broker 故障或 __consumer_offsets Leader 迁移

Rebalance 期间,部分或全部 Consumer 需要暂停消费,重新获得分配后再继续。

分区分配策略

策略特点
Range按 Topic 范围分配,实现简单,但多 Topic 时可能倾斜
RoundRobin在所有 Consumer 间轮询分配,更均匀
Sticky尽量保持上次分配,减少 Partition 迁移
CooperativeSticky渐进式再均衡,减少全量暂停

Range 示例

Topic A: P0 P1 P2
Topic B: P0 P1 P2
Consumers: C1 C2

Range:
C1 -> A-P0 A-P1, B-P0 B-P1
C2 -> A-P2,      B-P2

多 Topic 场景下,Range 可能让 C1 明显更忙。

Cooperative Rebalance

传统 Rebalance 通常是“全部撤销再重新分配”。Cooperative Rebalance 会尽量只迁移需要变化的 Partition。

旧分配:
C1 -> P0 P1
C2 -> P2 P3

新增 C3 后:
第一轮:C1 释放 P1,C2 释放 P3
第二轮:C3 接收 P1 P3

这样可以减少 stop-the-world 时间,适合消费链路比较重的服务。

Static Membership

如果 Consumer 重启很快,普通动态成员机制仍可能触发 Rebalance。Static Membership 允许给实例设置稳定身份:

properties
group.instance.id=order-consumer-1

适用场景:

  • Kubernetes StatefulSet
  • 固定实例编号的服务
  • 重启频繁但实例身份稳定的消费程序

优点是短暂重启时可以减少不必要的 Rebalance。注意 group.instance.id 必须全局唯一,不能多个实例复用。

Offset 存储

Consumer Offset 存在 Kafka 内部 Topic:

text
__consumer_offsets

Offset Key 大致包含:

text
group.id + topic + partition

Offset Value 包含:

  • 已提交 Offset
  • Metadata
  • Commit 时间
  • Expire 时间

查看消费位置:

shell
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
  --describe --group order-service

常见问题

Rebalance 频繁

排查方向:

  • max.poll.interval.ms 是否太小
  • 单批处理是否太慢
  • Consumer 是否频繁重启
  • session.timeout.ms 是否过于敏感
  • 是否使用了会频繁变化的正则订阅

Consumer 数量增加但吞吐没提升

检查:

  • Consumer 数量是否已经超过 Partition 数
  • 单 Partition 是否成为热点
  • 下游数据库或外部服务是否才是瓶颈
  • max.poll.records 和处理线程模型是否合理

Lag 高但 CPU 不高

可能原因:

  • Consumer 分配到的 Partition 太少
  • Fetch 配置太保守
  • 单条消息处理阻塞在外部 IO
  • Offset 提交失败导致反复消费

参考