先说个得罪人的话:网上那些选型对比表,九成是抄的
2021 到 2023 年我在一家做订单中台的公司,中间件这块基本是我一个人扛。那两年我干过最蠢的事,就是拿了张某度搜来的「四大消息队列对比表」去给领导汇报,表里写着 Kafka 吞吐百万级、RabbitMQ 两万级,然后我拍脑袋选了 Kafka。结果我们日均消息量 40 万条、峰值 QPS 800 都不到,用 Kafka 属于高射炮打蚊子——三台 broker 的监控、磁盘清理、告警配置,每周多花我两天。
后来换过 RabbitMQ,短暂试过 Pulsar,最后稳定在 RocketMQ,另外拿 MySQL 建了张兜底表。这篇文章我不再列那种谁强谁弱的表格,那种表一抓一大把,参数还都是官方 best case 跑出来的,跟你业务没关系。我想聊的是几个具体的、能查证的、你上线之后真的会撞上的数字。
先给一组我自己环境下的实测,免得说我光张嘴。三台 12C 24G + SSD 的机器,千兆内网,消息体 1KB:Kafka 3.6 三节点、3 分区、acks=1、batch.size 128KB 的时候,单 broker 稳定 8 万条/s 左右,端到端 p99 大概 12ms;把 acks 改成 all、min.insync.replicas=2,吞吐直接掉到 4.5 万条/s,p99 涨到 25ms 上下。这个落差很多对比表是不写的,但它是你生产环境的真实成本。
真正该看的三个数字,跟峰值吞吐都没关系
第一个数字:凌晨三点出问题的时候,你们的值班同学能不能看懂这个中间件的日志。Kafka 的 rebalance 日志、RabbitMQ 的 Erlang crash dump、Pulsar 的 BookKeeper 副本恢复日志,这三样的可读性完全不在一个量级。我们那次 RabbitMQ 脑裂,两个节点互相以为对方死了,队列直接不可用,值班的兄弟盯着日志看了四十分钟才定位。
第二个数字:你们有没有流计算或者 CDC 的需求。如果有,Kafka 基本是唯一解——Flink、Spark Structured Streaming、Debezium 全都是一等公民支持,接 RocketMQ 你得自己写 connector,接 RabbitMQ 更别提。反过来,如果你只是业务解耦、异步通知、削峰填谷,那 Kafka 的生态优势一点用不上。
第三个数字,也是我觉得最被忽略的:消息丢了以后,你能不能补回来。如果你的上游本身就有 binlog,或者有定时对账任务,消息丢了能重放,那可靠性配置完全可以降一档——RocketMQ 从同步刷盘改成异步刷盘、Kafka 的 acks 从 all 降到 1,性能往往能翻一倍,换来的是极端情况下丢几秒数据。这个取舍比「哪个 MQ 更强」重要得多。
Pulsar 那段经历,和 RabbitMQ 的几个硬参数
Pulsar 我是 2022 年底试的,3 个 broker + 3 个 bookie + 1 个 ZooKeeper,想着存算分离能弹性扩。跑了两个月放弃,原因不复杂:一层变三层,出问题排查路径太长。有次 bookie 磁盘写满到 95%,broker 端开始长时间卡住,客户端超时,但 broker 日志里就一句含糊的写入失败。另外单 topic 我压到 4 万条/s 左右,p99 就有 30ms 往上了,比 Kafka 因为多绕了一层,延迟这块确实吃亏。它适合的场景是有几千上万个 topic、需要多租户隔离的大团队,小团队上这个,运维成本会教你做人。
RabbitMQ 有几个参数值得单独拎出来说。3.8 之后官方主推 quorum queue(基于 Raft),老的 mirrored classic queue 官方已经不推荐了。quorum queue 至少 3 节点,内存水位默认 vm_memory_high_watermark 是 0.4,也就是说内存用到 40% 左右就开始阻塞生产者,这个数字很多人不知道,等到连接被 block 了才去查。单队列吞吐我实测大概 1 万条/s 之后就明显开始堆积,想再往上要么加队列要么加消费者,但队列一多,Erlang 进程数就上去了。
我踩过的 7 个坑,按疼的程度排
-
RabbitMQ 镜像队列脑裂。网络抖了十几秒,两个节点各选各的 master,队列卡死。最后是停掉两个节点、删掉镜像队列配置、重启才恢复。3.8 之后建议直接上 quorum queue,别留恋 classic。
-
Kafka 分区扩容导致顺序错乱。一开始 12 个分区,双十一前手贱扩到 60 个,结果 key 的 hash 落点全变了,同一个订单 ID 的消息跑到不同分区,消费端顺序全乱。要扩分区,先想清楚你的 key 是不是必须有序。
-
Kafka acks=1 + 副本不足。有一台 broker 挂了,它上面还没同步到 follower 的消息就没了,一晚上丢了大概 300 多条。后来改成 acks=all + min.insync.replicas=2,代价就是上面说的吞吐腰斩。
-
RocketMQ 异步刷盘 + 主从异步,机房断电丢了大概 5 秒的数据。这个配置本身没问题,问题是当时没人告诉我这个组合会丢数据。ASYNC_FLUSH + ASYNC_MASTER,快是真快,风险也是真风险。
-
Redis List 当队列,没做 ACK 机制。消费者 BRPOP 拿到消息、业务还没处理完,进程被 kill 了,那条消息就永远消失了。后来改成 BRPOPLPUSH 到备份队列,处理完再 LREM,才勉强能用。但这不是 MQ,别当 MQ 用。
-
Pulsar 的 bookie 磁盘写满。前面提过了,不多说,只提醒一句,BookKeeper 的磁盘告警阈值一定要设到 80% 以下。
-
MySQL 当队列,历史数据没归档。单表干到 8000 万行之后,select ... for update skip locked 的查询开始走全表,最夸张的一次删历史数据的 delete 把主从延迟拉到了 20 分钟。用 MySQL 做队列可以,但一定要有归档脚本,我们后来是每天凌晨删 7 天前的。
消费堆积了到底怎么查,给个能照着做的顺序
第一步,先确认是堆积还是消费挂了。Kafka 用 kafka-consumer-groups.sh --bootstrap-server xxx --describe --group 你的group,看 LAG 那一列;RocketMQ 用 mqadmin consumerProgress -g 你的group。如果 LAG 在稳定增长,是消费能力不够;如果 LAG 突然跳到几百万,大概率是消费端挂了或者卡住了。
第二步,看是不是在反复 rebalance。Kafka 日志里搜 Rebalance 或者 Member has left;如果十几分钟一次,八成是 max.poll.interval.ms(默认 300000,也就是 5 分钟)设小了,而你的单批处理时间超过了它。这种情况先把 max.poll.records 从默认 500 调小,比如 100,让每批处理快点。
第三步,看消费者数量。Kafka 里同一个 group 的消费者数超过分区数,多出来的实例是空转的,这地方坑过不少人——你以为加了机器就好了,其实分区数不够。RocketMQ 的消费并行度受队列数限制,道理一样。
第四步,真的是消费能力不够,优先加分区/加队列再加实例,别一上来就无脑堆消费者。另外看一眼是不是有慢 SQL 或者下游接口超时在拖后腿,我们那次堆积查到底,是下游一个查用户信息的接口 p99 从 20ms 涨到了 800ms。
我的一点私货
选型这事,我现在的判断顺序是:先看团队有几个人、会不会半夜爬起来看日志,再看有没有流计算/CDC 需求,最后才看吞吐。年消息量低于 10 亿、峰值 QPS 低于几千的,RabbitMQ 或者 RocketMQ 单集群足够,别上 Kafka,更别碰 Pulsar。
还有个观点可能有人不爱听:大部分团队的消息队列问题,根本不在 MQ 本身,而在消费端的幂等和顺序。你从 Kafka 换成 RocketMQ,顺序问题一样存在。我见过太多团队花两个月做中间件选型,结果业务代码里的重复消费问题一个都没解决。
最后,Kafka 4.0 已经把 ZooKeeper 摘掉了,全走 KRaft,如果你们还在 2.x,升级的时候留意一下这个变动。别问我怎么知道的。