1. 关于“多消费者”:消费者组(Consumer Group)是关键
Kafka 通过 消费者组(Group ID) 来决定消息是分发给所有人,还是只分发给一个人。
场景 A:广播模式(Pub/Sub) -> 消息被多次消费
如果你希望一条消息被多个不同的系统同时处理(例如:一条“订单创建”消息,既要发给“库存系统”扣减库存,又要发给“积分系统”增加积分,还要发给“大数据系统”做分析)。
- 做法:让这三个系统的消费者使用 不同的 Group ID。
- 库存系统消费者:
group.id = inventory-service
- 积分系统消费者:
group.id = points-service
- 大数据系统消费者:
group.id = bigdata-service
- 结果:Kafka 会把这条消息完整地推送给这三个组。每个组都会独立地消费一遍这条消息。
- 类比:就像发报纸。邮局(Kafka)把同一份报纸投递给了订阅它的三家不同的公司(不同的组)。每家都能看到。
场景 B:负载均衡模式(Competing Consumers) -> 消息只被消费一次
如果你希望提高处理速度,让多个消费者实例共同分担一个任务(例如:有10万条短信要发,你启动了5个消费者实例来加速发送)。
- 做法:让这5个消费者实例使用 相同的 Group ID。
- 所有实例:
group.id = sms-sender-group
- 结果:Kafka 会将 Topic 的分区(Partitions)分配给这5个实例。每条消息只会落入其中一个分区,因此只会被组内的某一个实例消费。其他实例不会收到这条消息。
- 类比:就像银行柜台。虽然有5个窗口(5个消费者实例),但属于同一个银行(同一个组)。一个客户(消息)来了,只能在一个窗口办理业务,不能同时在5个窗口办理。
| 消费者组配置 | 行为模式 | 典型应用场景 |
| 不同的 Group ID |
广播 (Broadcast) |
数据分发:一条日志同时用于实时监控、离线分析、故障报警。 |
| 相同的 Group ID |
负载均衡 (Load Balance) |
横向扩容:多个实例并行处理大量订单、短信、请求。 |
2. 关于“消息还在吗”:基于日志的持久化
传统消息队列(如 RabbitMQ)通常是“消费即删除”(ACK 机制),一旦消费者确认收到,消息就从队列中移除。
Kafka 完全不同。Kafka 本质上是一个分布式提交日志(Distributed Commit Log)。
-
消费不删除:当消费者读取消息时,它只是记录了一个 Offset(偏移量),表示“我已经读到这里了”。消息本身依然静静地躺在磁盘的文件里。
-
重复消费能力:因为消息没删,如果你的代码出错了,或者你想重新跑一遍数据,你可以手动重置 Offset,把之前的消息再读一遍。这是 Kafka 强大的地方。
-
什么时候删除?
消息的删除由 Retention Policy(保留策略) 控制,与是否被消费无关。默认策略有两种:
- 基于时间(默认):消息保存一定时间后删除。
- 配置项:
log.retention.hours (默认 168 小时,即 7 天)。
- 例子:即使消息刚被消费完,只要没到7天,它还在磁盘上。
- 基于大小:当分区文件总大小超过限制时,删除最早的消息。
(注:还有一个特殊的 cleanup.policy=compact 模式,用于去重保留最新值,常用于数据库同步场景,但默认是 delete 模式)
总结图示
想象 Kafka 是一个巨大的录像带库(磁盘上的日志文件):
- 生产者:负责不停地往录像带里录制内容(写入消息)。
- 消费者:是观看录像带的人。
- 如果一群人属于同一个组,他们商量好分工,A看第1-10分钟,B看第11-20分钟(负载均衡)。
- 如果一群人属于不同的组,每个人都拿一份拷贝,从头看到尾(广播)。
- 删除机制:不管有多少人看过录像带,录像带都不会立刻销毁。只有当录像带存满了仓库,或者录像带太旧了(超过7天),管理员才会把最早的那几卷扔掉。
核心结论
- 想多人消费? 设置不同的
group.id。
- 想多人分担? 设置相同的
group.id。
- 消息会消失吗? 不会因为被消费而消失,只会因为过期或磁盘满而被清理