选型先问三个问题
消息队列是后端架构里的高速公路,选错了再换代价极大。选型前先问自己三个问题:第一,业务需要多高的吞吐?是每秒几百条还是每秒几十万条;第二,延迟敏感吗?是允许秒级延迟还是要求毫秒级投递;第三,消息语义要多强?能不能容忍丢失,需不需要严格顺序,要不要消息回溯。
这三个答案基本决定了候选范围:吞吐和延迟决定引擎类型,语义决定使用方式。很多团队栽在把"吞吐高的"和"延迟低的"当成同一个东西,其实 Kafka 的高吞吐和 RabbitMQ 的低延迟走的是完全不同的设计路线。
- 吞吐单位时间能处理的消息量,决定横向扩展。
- 延迟消息从生产到可消费的间隔时间。
- 语义至少一次、至多一次、恰好一次的投递保证。
- 模型队列模型与发布订阅模型的差异。
三种消息队列的定位
RabbitMQ 是最老牌的消息中间件之一,基于 AMQP 协议,以交换器加队列的灵活路由著称,单机吞吐在每秒几万条量级,胜在功能完整、生态成熟、运维友好,适合企业内部系统集成与复杂路由场景。
Kafka 是分布式日志流的代名词,以追加日志和消费者组为核心,单集群吞吐轻松到每秒百万条,天然支持消息回溯与多消费者,适合大数据管道、日志采集、事件流与削峰填谷。Pulsar 则是后起之秀,把存储和计算分离,用 BookKeeper 做分层存储,支持多租户与秒级扩容,吞吐与 Kafka 相当,同时在消息模型上更贴近 RabbitMQ 的灵活路由。
# 一句话概括三者的典型定位
RabbitMQ = 灵活路由 + 可靠投递(企业集成)
Kafka = 高吞吐 + 日志回放(数据管道)
Pulsar = 多租户 + 存储计算分离(云原生)
吞吐与延迟对比
吞吐的核心在磁盘与网络的使用方式。RabbitMQ 每条消息都要确认并落盘,为可靠性牺牲了批量能力,延迟通常在毫秒到十毫秒级,但吞吐上到万级后需要靠集群分摊。Kafka 采用顺序写磁盘加批量发送,把随机 IO 变成顺序 IO,单分区即可支撑较高吞吐,代价是端到端延迟通常十毫秒以上,追求低延迟反而要控制批量大小。
Pulsar 的存储层独立成 BookKeeper,写入先落 journal 再异步刷盘,兼顾吞吐与延迟,分段存储让扩容不搬数据。选择时记住:要极低延迟且吞吐中等,选 RabbitMQ;要极高吞吐且能接受一定延迟,选 Kafka;要云原生多租户、吞吐与延迟都要,选 Pulsar。
# Kafka 生产端关键参数(吞吐优先)
batch.size=16384 # 批次越大吞吐越高
linger.ms=5 # 攒批等待时长
compression.type=lz4 # 压缩减小网络传输
acks=all # 副本确认保证不丢
消息模型与消费语义
RabbitMQ 是队列模型加交换器路由:生产者把消息交给交换器,交换器按绑定规则投到队列,每个队列的消息只会被一个消费者消费,多个消费者按轮询或公平分发竞争同一队列。Kafka 是分区日志模型:主题分成多个分区,每个分区的消息按序追加,同一消费组内的消费者各领分区,一个分区同时只给一个消费者。
- 至少一次可能重复,业务必须幂等。
- 至多一次可能丢失,适合可容忍丢数据的场景。
- 恰好一次事务加幂等,成本最高。
# Kafka 消费者:至少一次语义的标配
props.setProperty("enable.auto.commit", "false");
// 业务处理成功后手动提交偏移量
consumer.commitSync();
做选型时把消息语义当作契约写进设计文档:允许重复就降低实现成本,要求恰好一次就要评估事务开销,Kafka 的幂等生产者和事务支持在吞吐与强语义之间给了折中。
高可用与运维成本
RabbitMQ 用镜像队列做高可用,主从复制粒度是队列,节点数翻倍时队列都要复制,容量增长不明显,运维上依赖 Erlang 节点互联,集群规模通常控制在几台。Kafka 的高可用靠分区副本,副本分布在不同 broker,ISR 机制保证主副本故障时从 ISR 里选新主,扩容就是加节点并迁移分区,运维体系成熟但概念多。
Pulsar 的 broker 无状态,扩容直接加 broker,存储交给 BookKeeper 自动均衡,多租户的 quota 与隔离开箱即用,运维最省心但组件更多,小团队上手门槛更高。运维成本往往是被忽略的选型变量:一个没人会运维的队列,再好的性能也撑不起生产。
选型决策与迁移
决策可以按场景矩阵收敛:业务系统集成、延迟敏感、路由复杂选 RabbitMQ;日志、埋点、事件流、大数据管道选 Kafka;多团队共享、多租户隔离、存储要长时间保留选 Pulsar。规模不大时不要为了"先进"选 Pulsar,也不要为了"省事"让 Kafka 硬扛所有场景。
迁移旧队列有三条路:双写过渡,新旧并行写一段时间再切读;消息桥接,用一个消费转发服务把旧队列消息搬到新队列;流量灰度,按消息类型逐步迁移。迁移期间幂等与顺序性最容易出问题,建议先迁移允许重复消费的流量,最后迁移强顺序的。
# 双写伪代码示意
def send(msg):
rabbit.send(msg) # 旧队列继续写
kafka.send(msg) # 新队列同步写
# 观察稳定后关闭 rabbit.send
落地清单
选型不是一次性的 PPT 汇报,而是一组可验证的工程决策。把吞吐、延迟、语义、运维四个维度写成量化指标,用压测数据说话,而不是听厂商宣传。落地上线前补齐以下清单:监控指标要覆盖生产速率、消费积压、端到端延迟;报警阈值要定在积压量超过预期水位;消费方全部做幂等,为至少一次语义兜底;消息体要带消息 ID 与时间戳,方便回溯与对账。
最后记住一句话:消息队列只是传输管道,真正的可靠性靠消费方的幂等与重试。选型把管道选对,落地把消费做稳,这套组合拳才是后端消息架构的正解。
- 量化吞吐、延迟、语义需求后再选型。
- 消费方全部幂等,容忍至少一次语义。
- 监控生产速率、消费积压与端到端延迟。
- 迁移采用双写或桥接,先迁可重复流量。