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 | 成员变化或订阅变化时重新分配 |
| 保存 Offset | Offset 写入内部 Topic __consumer_offsets |
| 检测故障 | 根据心跳和超时判断 Consumer 是否存活 |
Consumer 如何找到 Coordinator:
group.id
│
├── hash(group.id)
├── 定位 __consumer_offsets 的某个 Partition
└── 该 Partition 的 Leader Broker 就是 CoordinatorJoinGroup 与 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.ms | Coordinator 多久没收到心跳就认为 Consumer 失效 |
max.poll.interval.ms | Consumer 两次 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 允许给实例设置稳定身份:
group.instance.id=order-consumer-1适用场景:
- Kubernetes StatefulSet
- 固定实例编号的服务
- 重启频繁但实例身份稳定的消费程序
优点是短暂重启时可以减少不必要的 Rebalance。注意 group.instance.id 必须全局唯一,不能多个实例复用。
Offset 存储
Consumer Offset 存在 Kafka 内部 Topic:
__consumer_offsetsOffset Key 大致包含:
group.id + topic + partitionOffset Value 包含:
- 已提交 Offset
- Metadata
- Commit 时间
- Expire 时间
查看消费位置:
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 提交失败导致反复消费