本页目录
消费组与协作
consumer group 是消费者对分区所有权的协商——本质是一个分布式共识变体。eager vs cooperative rebalance 决定了再平衡时整个 group 是停止还是继续消费。
概述
Kafka 架构讲了 partition 和 ISR——这是存储/复制层的分布式机制。但消费者一侧同样有分布式协调问题:多个消费者共享一个 topic 的分区,谁消费哪个分区?有人挂了怎么重新分配?分配期间消费停不停?这是消费组(consumer group)解决的问题——本质是"一组消费者对分区的所有权协商",一个不折不扣的分布式共识变体。
Consumer group 是怎么工作的
Kafka 的消费模型:一个 partition 在一个 consumer group 内只能被一个 consumer 消费。 这是保证"同 partition 内消息有序"的前提——如果两个 consumer 同时读同一个 partition,它们各自看到的 offset 区间不连续,顺序就乱了。
关键:partition 数决定并行度上限。6 个 partition → 最多 6 个 consumer;第 7 个 consumer 加入后会闲置(没有 partition 分配给它)。要扩展消费并行度就要加 partition,不是加 consumer。
再平衡(rebalance):所有权的协商
当 consumer 加入或离开 group 时,触发再平衡——重新分配 partition 所有权。这是 consumer group 机制里最关键也最容易出问题的一环。
eager rebalance(旧协议):停掉再分
1. 新 consumer C4 加入 → group coordinator 通知所有 consumer: 释放当前分配
2. 所有 consumer 停止消费、释放 partition 所有权
3. 重新分配: C1←P0,P1 C2←P2 C3←P3,P4 C4←P5
4. 所有 consumer 从上次 committed offset 恢复消费
这个过程叫 stop-the-world:步骤 2-3 期间整个 consumer group 停止消费,延迟堆积。对于延迟敏感的流处理应用,每次 rebalance 都是事故。
cooperative rebalance(新协议,KIP-429):渐进式
1. C4 加入 → coordinator 计算新分配,只通知"需要释放 partition"的 consumer
2. 受影响的 consumer 释放被夺走的 partition,继续消费未受影响的 partition
3. 新 owner(C4)接手释放的 partition
4. 整个过程中,未受影响的 consumer 不停
本质差别:eager 让所有人释放再重新分配;cooperative 只动受影响的,其余不停。从"全部停"变成"部分不停",大幅降低 rebalance 的业务影响。
sticky assignment:尽量保持已有分配
rebalance 后的分配不仅看"均匀",还要看"最小变动":如果一个 consumer 已经在消费某个 partition 且不需要释放,就保持不动。这叫 sticky——避免无谓的分配变动,减少 consumer 状态切换。
offset 管理:消费进度的"持久化指针"
consumer 读到哪儿了,需要记下来——这就是 offset。管理方式有两种:
| 自动提交 | 手动提交 | |
|---|---|---|
| 时机 | 按时间间隔(如每 5 秒)自动提交当前 offset | 应用程序显式调用 commitSync() / commitAsync() |
| 风险 | process 完成但未到提交间隔 → crash → 重复消费(at-least-once) | process 完成 → commit → crash → 不重复; process 完成后但 commit 前 crash → 重复 |
| 适用 | 容忍少量重复 | 需要精细控制(如 exactly-once 消费流程) |
offset 存在哪儿:Kafka 的 __consumer_offsets 内部 topic(多 partition、有 replication),consumer group 的 offset commit 是一条写进这个 topic 的消息——Kafka 用自己的存储机制存消费进度。
exactly-once 消费:把消费端的事务拼起来
消息语义讲了 producer 端的 exactly-once(幂等 producer + 事务)。消费端怎么做 exactly-once?核心是把消费 offset 的提交和处理结果的输出放在同一个事务里:
消费者事务:
1. 从 topic-A 读消息 (offset=N)
2. 处理 → 写结果到 topic-B
3. 提交 offset(N+1) 到 __consumer_offsets
步骤 2 和 3 在同一个 Kafka 事务里:
- 事务成功: 结果写入 topic-B, offset 前进到 N+1 → 消费"发生过"
- 事务失败/消费者 crash: 结果未写入, offset 回滚到 N → 消息会被重新消费
关键要求:消费的 isolation.level 必须设为 read_committed——只读已提交事务的消息,忽略 abort 的事务和未完成的 dangling 事务。
这整套组合(幂等 producer + 事务 producer + read_committed consumer + offset 和输出在同一事务)构成了 Kafka 的 exactly-once 语义——不是理论上的"完美仅一次",而是工程上"效果等同于仅一次,不会出现重复或丢失"。
RabbitMQ 的对比:队列模型 vs 日志模型
同一章里的另一套思路:RabbitMQ 用队列而非日志。消息被确认后删除(不是 append 到不可变 log),没有 replay/回溯,没有 partition 内的全局顺序。consumer group 协调也不同:RabbitMQ 的 consumer 不协商 partition 所有权,而是 broker 按 round-robin 或 consistent-hash exchange 分发消息——更简单,但没有 Kafka 的"同 key 到同 partition 保证顺序"。
选 Kafka 的日志模型,是因为分布式流处理需要回放(replay)和全局有序——这两件是队列模型做不了的。Kafka 的 partition 本质是一个分片的、不可变的、可回放的 log,consumer group 是这个 log 之上的"谁读哪个分片"的协商层。
常见问题与修法
- 再平衡风暴:consumer 频繁加入/离开(如心跳超时阈值太短、处理时间过长 → 被认为挂了)。每次 rebalance 导致整个 group 停顿。→ 设
max.poll.interval.ms足够大(大于最长处理时间),用 cooperative rebalance,监控 rebalance 频率。 - partition 数 < consumer 数:多余的 consumer 闲置 → 要么加 partition,要么接受闲置(consumer 作为热备)。
- 消费滞后(lag):consumer 处理速度跟不上 producer 写入速度 → 监控 lag(consumer offset vs partition end offset),加 consumer 或加 partition。
- 自动提交 + 异步处理:consumer 把消息扔进线程池就提交 offset → 异步线程处理失败时消息已"消费完成"→ 丢失。→ 要么同步处理,要么手动 commit 在异步处理完成后。
参考
- Kafka 文档: Consumer Group Protocol、KIP-429 (Incremental Cooperative Rebalancing)
Keywords: consumer group, rebalance, eager, cooperative (incremental), sticky assignment, stop-the-world, group coordinator, offset management, __consumer_offsets, exactly-once consumption, isolation.level, read_committed, transactional consumer, 消费滞后, consumer lag, rebalance storm, Kafka vs RabbitMQ, log vs queue