Kafka、RabbitMQ 与队列语义
Kafka 和 RabbitMQ 都能在生产者与消费者之间传递消息,但保存方式、消费位置和路由模型不同。选择组件时应先确定顺序、保留、重放和路由需求,再比较吞吐与运维成本。
1. 消息队列隔离生产与消费速度
生产者把工作写入中间件后,可以先结束当前请求;消费者按自己的容量异步处理。队列能够吸收短时流量峰值,也让消费者故障不必立刻拖垮生产者。
这种解耦增加了新的状态:消息是否已经写入、是否已经投递、消费是否完成、确认是否成功,都可能在不同时间发生。系统必须接受延迟、重复、积压和局部不可用,而不是把一次异步发送当成本地方法调用。
2. Kafka 把记录追加到分区日志
Kafka topic 由多个 partition 组成。生产者把 record 追加到某个 partition,分区内按 offset 形成有序日志。消息消费后不会立即从日志删除,而是按保留时间或大小策略清理。
消费者保存自己已经处理到的 offset,因此可以从旧位置重新读取。相同数据还可以由不同 consumer group 独立消费,适合事件流、审计、数据管道和需要重放的处理链路。
分区是并行和顺序的共同边界:同一个 consumer group 中,一个 partition 同时只分配给一个 consumer;消费者数量超过分区数时,多出的消费者没有分区可处理。
3. RabbitMQ 通过 exchange 路由到 queue
RabbitMQ 的生产者通常把消息发布到 exchange,exchange 根据类型、routing key 和 binding 把消息路由到一个或多个 queue。消费者从 queue 接收 delivery,并在处理完成后确认。
常见 exchange 类型包括:
- direct:routing key 精确匹配。
- topic:按点分单词和通配符匹配。
- fanout:发送到所有已绑定 queue。
- headers:根据消息头属性匹配。
同一条业务消息可以通过不同 binding 复制到多个 queue,让不同业务组各自消费。队列中的消息在确认并满足相应条件后才从该队列移除。
4. 两者的消费进度保存方式不同
Kafka broker 保存日志,消费者以 offset 表示读取位置。提交 offset 只是在日志中记录消费进度,消息本身仍可能被再次读取。
RabbitMQ 记录消息是否已经向某个消费者投递和确认。未确认连接断开时,消息通常可以重新入队并投递给其他消费者。消息确认后,broker 不再为该 queue 保留这次 delivery。
所以,Kafka 更自然地支持按时间回放和多个独立视图;RabbitMQ 更自然地表达复杂路由、工作队列和逐条确认。两者都可以完成很多相似工作,但默认模型会影响实现复杂度。
5. 顺序只能在受限范围内成立
Kafka 只保证单 partition 内 record 的顺序。需要同一业务键有序时,生产者应稳定地把该键映射到同一 partition。增加分区、修改分区算法或让消费者内部并发处理,都可能改变业务观察到的完成顺序。
RabbitMQ queue 有入队顺序,但多个消费者并发、不同处理耗时、消息重新入队和优先级队列都会让完成顺序发生变化。要求严格串行时,需要限制同一键的并发,并处理失败消息阻塞后续工作的代价。
6. 投递语义取决于写入、确认和业务提交顺序
- At-most-once:先确认或推进进度,再处理;失败时可能丢消息,但通常不重复。
- At-least-once:先完成处理,再确认;确认失败或进程崩溃时会重复,但不轻易丢失。
- Exactly-once:只在明确的系统和事务边界内成立。
Kafka 的幂等生产者和事务可以在 Kafka 内避免部分重复写入,并把 Kafka 读写与 offset 提交放进同一事务。若消费者还要写 MySQL、调用支付或发送邮件,外部副作用仍需幂等、Outbox 或业务状态机。
7. 根据工作负载选择组件
优先考虑 Kafka 的情况:
- 需要保留和重放较长时间的事件流。
- 多个消费组要独立处理同一份记录。
- 需要按分区扩展高吞吐流式处理。
- 下游需要从任意 offset 恢复或回溯。
优先考虑 RabbitMQ 的情况:
- 需要 direct、topic、fanout 等灵活路由。
- 任务队列强调逐条确认、优先级、TTL 或死信。
- 消息处理后不需要长期保留和任意重放。
- 希望 broker 直接管理未确认 delivery 与消费者流量。
最终还要比较团队运维经验、容量、故障恢复和客户端生态。不要仅根据“吞吐更高”选择消息系统。
8. 常见问题
8.1 Kafka 是否就是一个普通队列
Kafka 可以承担队列用途,但它保存的是可保留、可重放的分区日志。消费组通过 offset 管理进度,消费消息不会立即删除日志中的记录,这与传统工作队列的默认生命周期不同。
8.2 消息队列能否自动削平任何流量峰值
只能吸收容量范围内的临时差值。若生产速率长期高于消费速率,积压会持续增长,最终触及磁盘、保留时间或业务时效上限。仍需扩容消费者、限制生产或降低单条处理成本。
9. 面试题
9.1 Kafka 和 RabbitMQ 的模型有什么区别,应该如何选择
出现公司:快手
考察重点
- 分区日志、offset、exchange、queue 与 acknowledgement。
- 保留重放、路由和顺序边界。
- 投递语义与外部副作用。
相关内容:第 2 节“Kafka 把记录追加到分区日志”至第 7 节“根据工作负载选择组件”。
参考回答
Kafka 把消息追加到 partition 日志,消费者通过 offset 保存进度,记录可以按保留策略长期存在并由多个 consumer group 独立重放;顺序只在单 partition 内保证。RabbitMQ 通过 exchange 和 binding 把消息路由到 queue,消费者处理后 ack,适合工作队列和复杂路由。
需要事件保留、回放、多消费组和分区吞吐时更倾向 Kafka;需要灵活路由、逐条 delivery 管理和任务队列能力时更倾向 RabbitMQ。两者都不会自动让外部业务只执行一次,写数据库或调用第三方仍需幂等和一致性设计。