消息队列成本:Kafka/RocketMQ 的存储与计算
消息队列这东西,平时看着不起眼,月底账单一出来,往往能吓你一跳。我见过不少团队,Topic 建了一堆,Partition 数拍脑袋定,结果集群吞吐上不去,磁盘倒是先爆了。
说白了,消息中间件的成本就两块:存储和计算。存储是磁盘,计算是 CPU 和内存。怎么规划 Topic 和 Partition,直接决定了这两块花多少钱。
存储成本:消息到底占了多少空间?
很多人以为消息存进去就完了,其实不然。Kafka 和 RocketMQ 的存储模型不一样,成本构成也不同。
Kafka 的存储模型
Kafka 每个 Partition 就是一个日志文件(Segment)。消息追加写入,顺序读写。好处是快,坏处是——不能原地修改。消息过期或者被消费完,只能等删除。
我习惯用一个公式估算 Kafka 的存储成本:
单 Partition 日存储量 = 消息平均大小 × 日消息量 × 副本数 × (1 + 索引开销比例)
索引开销比例 ≈ 10%~15%
举个例子。假设消息平均 1KB,日吞吐 1 亿条,副本 3,索引开销 10%。
日存储 = 1KB × 1亿 × 3 × 1.1 ≈ 330GB
月存储 ≈ 10TB
嗯,10TB。这还只是一个 Topic。你想想看,如果 Topic 有几十个,磁盘成本会涨成什么样。
关键点:Kafka 的存储成本与 保留时间 强相关。默认保留 7 天,如果你改成 30 天,存储成本直接翻 4 倍多。
RocketMQ 的存储模型
RocketMQ 用的是 CommitLog + ConsumeQueue 的架构。所有消息写到一个共享的 CommitLog 里,然后通过 ConsumeQueue 做逻辑索引。
好处是:写放大更小。同一个 Broker 上的多个 Topic 共享一个 CommitLog,磁盘利用率更高。
但 RocketMQ 有个坑——文件预分配。每个 CommitLog 文件默认 1GB,创建时就占满磁盘空间。如果你 Topic 多但消息量少,磁盘浪费会很严重。
注意:RocketMQ 的 ConsumeQueue 是固定大小的(每个条目 20 字节),但 CommitLog 的预分配机制会导致「看起来用了 100GB,实际有效数据只有 30GB」的情况。我在项目中遇到过,一个低流量的 Topic 占了 10 个 CommitLog 文件,实际消息不到 500MB。
计算成本:CPU 和内存都花在哪了?
计算成本往往比存储更隐蔽。你加机器不一定是因为磁盘不够,而是 CPU 扛不住了。
Kafka 的计算开销
Kafka 的计算主要花在三个方面:
- 网络 I/O:每个 Partition 对应一个网络连接。Partition 越多,网络线程越忙。
- 磁盘 I/O:刷盘、Compaction、索引维护。
- 内存:Page Cache 占大头。Kafka 重度依赖 OS 的 Page Cache 来加速读写。
我见过一个案例:某团队把 Topic 分了 200 个 Partition,结果每个 Broker 上的网络线程全部打满,CPU 飙到 90%。后来合并成 50 个 Partition,CPU 直接降到 30%。
经验:Kafka 的 Partition 数建议控制在 Broker 数 × 磁盘数 × 2~3 之间。比如 3 个 Broker,每个 2 块盘,Partition 总数在 12~18 比较合理。
RocketMQ 的计算开销
RocketMQ 的计算开销主要在:
- 刷盘策略:同步刷盘 vs 异步刷盘。同步刷盘 CPU 开销高 2~3 倍。
- 消息过滤:Tag 过滤和 SQL 过滤。SQL 过滤会走表达式解析,CPU 消耗大。
- 主从同步:Dledger 模式下的 Raft 协议,CPU 和网络开销都不小。
我曾经帮一个客户优化 RocketMQ 集群,发现他们所有 Topic 都开了同步刷盘 + SQL 过滤。实际上 80% 的消息不需要这么高的可靠性。改成异步刷盘 + Tag 过滤后,CPU 使用率从 70% 降到了 25%。
Topic 与 Partition 的规划策略
规划 Topic 和 Partition,说白了就是在性能和成本之间找平衡。
Topic 规划原则
- 按业务域划分:一个业务域一个 Topic,不要一个 Topic 塞所有消息。
- 控制 Topic 总数:Kafka 建议单个集群不超过 1000 个 Topic,RocketMQ 建议不超过 500 个。
- 区分实时和离线:实时 Topic 保留时间短(1~3 天),离线 Topic 保留时间长(7~30 天)。
避坑指南:我曾经见过一个团队,把每个微服务的每个事件类型都建一个 Topic,结果 Topic 数超过 2000 个。Kafka Controller 频繁做 Leader 选举,整个集群不稳定。后来合并成 50 个 Topic,问题解决。
Partition 规划原则
| 因素 | Kafka | RocketMQ |
|---|---|---|
| 单 Partition 吞吐上限 | 约 10MB/s(取决于磁盘) | 约 5MB/s(取决于 CommitLog) |
| Partition 数上限 | 单 Broker 建议 ≤ 200 | 单 Broker 建议 ≤ 100 |
| 扩容方式 | 只能增加,不能减少 | 可以动态调整 |
| 存储隔离 | 每个 Partition 独立文件 | 共享 CommitLog |
我个人的习惯是:先估算吞吐,再定 Partition 数。
Partition 数 = 预估峰值吞吐 / 单 Partition 吞吐上限 × 1.5(预留缓冲)
举个例子。预估峰值吞吐 100MB/s,Kafka 单 Partition 上限 10MB/s。
Partition 数 = 100 / 10 × 1.5 = 15
嗯,15 个 Partition 就够了。没必要为了「以后可能扩容」而一开始就设 50 个。
成本优化实战技巧
下面是我在实际项目中用过的几个优化手段,效果都不错。
1. 合理设置保留时间
很多团队默认保留 7 天,其实大部分消息 1 天内就被消费完了。我建议:
- 实时 Topic:保留 1~3 天
- 离线 Topic:保留 7~14 天
- 日志 Topic:保留 3~7 天
别小看这个改动。一个日吞吐 1 亿条的 Topic,保留时间从 7 天改成 3 天,存储成本直接降 57%。
2. 压缩消息体
Kafka 和 RocketMQ 都支持消息压缩。我习惯用 Snappy 或 Zstd 算法。
// Kafka 生产者配置
props.put("compression.type", "zstd");
// RocketMQ 生产者配置
producer.setCompressMsgBodyOverHowmuch(1024); // 超过 1KB 的消息自动压缩
压缩率一般在 50%~70%。也就是说,1TB 的消息压缩后只剩 300~500GB。
3. 控制副本数
副本数不是越多越好。我见过有人设 5 副本,说是「为了高可用」。其实 3 副本已经足够。
警告:每增加一个副本,存储成本增加 100%,网络开销也翻倍。如果你的集群有 10 个 Broker,3 副本已经能容忍 2 台机器同时宕机。没必要设 5 副本。
4. 使用 Tiered Storage(分层存储)
Kafka 2.8+ 和 RocketMQ 5.0+ 都支持分层存储。把热数据放本地 SSD,冷数据放对象存储(如 S3、OSS)。
我帮一个客户做过迁移:本地 SSD 保留 1 天数据,对象存储保留 30 天。本地磁盘成本降了 80%,对象存储成本只有原来的 20%。
一张图看懂消息队列成本构成
下面这张图总结了消息队列成本的核心要素和优化方向。
总结一下
消息队列的成本优化,说白了就是三件事:
- 存多少:控制消息大小、保留时间、副本数
- 怎么算:合理规划 Partition、选择合适的刷盘和压缩策略
- 怎么存:热冷分离,用分层存储降低冷数据成本
我见过太多团队在消息队列上花冤枉钱。其实只要把 Topic 和 Partition 规划好,再配合压缩和分层存储,成本降 30%~50% 是很轻松的事。嗯,下次你看到消息队列的账单时,不妨先看看这几个指标。