《Java 消息中间件·可靠消息与消费治理》15.2 容量与成本核算

可靠性每一项配置都有账单:副本数乘存储、acks 乘内部复制流量、保留期乘天数、跨集群乘出网。本篇不谈具体价格,只给可核算的量——一天 1000 万条 1 KiB 消息,三档方案各需要多少 GB·天、多少出网流量,以及故障时才支付的副本重建成本。每一档都标出它能防住哪种故障,以及哪一笔钱其实可以不花。

先谈量,再谈钱

谈成本最容易滑向两个坑:一是直接报一个「每 GB 每月多少钱」,可这个数字随厂商、地域、合约、是否自建变化,抄过去就是错的;二是只算存储、不算网络和人力,得出的结论系统性偏低。

本篇只做第一件事:把每一种可靠性配置换算成可核算的量。你要的存储是 GB·天,你要的出网流量是 GiB/月,你要的运维动作是「每一次副本重建要搬多少数据」。拿到这些量之后,乘你自己手里的单价,就是你的账单。

所有算例统一用同一组假设,方便你对号入座:

假设取值
每日业务消息量1000 万条
单条业务载荷1 KiB(未压缩)
消息格式v2 record batch(Kafka 4.x 默认)
存储余量在载荷基础上留 5%,覆盖批次头、索引文件与段尾未满部分
网络口径只算集群内外出网;同机房内网不计费但计带宽

按这组假设,一天的业务载荷是 10,000,000 × 1024 = 10,240,000,000 字节,约 9.54 GiB。加上 5% 余量,取整记作 每天 10 GiB。后面所有数字都从这个 10 出发。

需要提醒一句:这 5% 余量的口径随批量大小变化。卷一讲过分区与批次,v2 格式的开销挂在批次上而不是消息上,你的生产端如果攒批量很大,实际余量会明显小于 5%;如果每条都单独发(linger.ms=0、批量极小),余量会超过 5%。10 GiB 这个数字在你自己的系统上要实测,不能照搬。

存储:三个乘数

Kafka 的磁盘占用不是「消息量」一个变量,而是三个乘数连乘:

占用 ≈ 每日数据量 × 保留天数 × 副本数

三个乘数对应三种完全不同的支出意图,分开看才看得清:

  • 每日数据量:业务决定的,只能靠压缩和减少消息数来降。
  • 保留天数:可靠性决定的,它买的是「出事之后还能回头重放多久」。
  • 副本数:可用性决定的,它买的是「同时坏几台机器还不丢」。

把它们混成一句「我们每天产生多少数据」是成本失控最常见的起点。三档方案在这三个乘数上的取值不同,结果差 8 倍以上:

方案每日数据保留副本存储占用
基础10 GiB3 天260 GiB
标准10 GiB7 天3210 GiB
强化10 GiB14 天3420 GiB
强化 + 跨集群10 GiB14 天(主) / 3 天(备)3 + 3420 + 90 = 510 GiB

从 60 GiB 到 510 GiB,业务数据量一天没变。多出来的 450 GiB 全是可靠性溢价。

副本数乘的是什么

副本不是「备份」,是热备——每一份都在持续接收写入、持续占盘、持续消耗 IO。RF 从 2 到 3,存储涨 50%,但买到的东西很具体:

  • RF=2:任意一台 broker 挂掉,数据仍在;但剩下的那台在重建期间如果再挂,就丢。
  • RF=3:任意一台挂掉,重建期间再挂一台,仍不丢。

重建不是瞬间完成的。一台 broker 上承载的可能是几十 GiB 数据,重建这几小时到一两天,正是 RF=3 与 RF=2 拉开差距的窗口。第二章讨论过重复来源,这里换个角度:RF=2 的重复风险不在同一秒,而在这段长长的重建窗口里。

保留天数乘的是什么

保留期买的是「故障被发现之前,数据还没被删」。这条因果链经常被算错:

保留天数 ≥ 故障潜伏期 + 定位时间 + 修复与重放时间

一次消费端逻辑错误,可能三天后才被业务方发现对账不平。如果保留期是 3 天,发现的那一刻,最早出错的那批数据已经滚出磁盘,你连重放都做不到,只能靠下游对账补数据。这时候省下的 70 GiB 存储,代价是一次人工数据修复。

反过来,保留期过长也是浪费。第七章讲 outbox、第九章讲 Saga,这些链路里消息一旦被消费并确认,长期留存的价值很低。按 topic 定保留期,不要全站一个值。

5% 余量之外还有两笔

上面算的是稳态占用。还有两笔不常出现在报表里:

段文件尾部未满。 每个分区当前正在写的那个段文件,大小在 0 到 segment.bytes 之间浮动。分区多的时候这笔不小:1000 个分区、段大小 1 GiB、按平均半段 500 MiB 估,就是约 488 GiB 的「半空段」,比上表里 210 GiB 的稳态数据还多。这跟消息量无关,纯粹由分区数 × 段大小决定。分区多而消息少的集群,这条经常是主要开销。

索引文件。 .index 与 .timeindex 的大小由 log.index.interval.bytes 决定,通常远小于数据文件,但如果把间隔调得很小(排查问题时有人会这么干),索引能涨到数据文件的几个百分点。

网络:第二笔账,也是最容易漏的

存储有账单,因为硬盘有价格。网络也有账单,但它在内网里往往「看不见」,直到跨了机房或跨了云。

把流量按方向拆开,每一项的口径不同:

流量量的算法是否计费
生产入流量每日数据量通常不计入网费,计带宽
副本复制(leader → follower)每日数据量 × (RF − 1)内网不计费,占带宽
消费出流量每日数据量 × 消费组数同机房不计费;跨区/跨云计费
副本重建单机承载量(一次性)内网,但与正常流量抢带宽
跨集群复制每日数据量计费,这是出网
灾备回切灾备端累积量计费,且量大

按 10 GiB/天、RF=3 算:

  • 副本复制:10 × 2 = 20 GiB/天,即正常写入流量的 3 倍在网络里流动(1 份入 + 2 份复制)。
  • 消费:每多一个消费组,多 10 GiB/天。第四章讲过「多组订阅」的代价,那篇讲的是组会互相拖累,这里的账是同一条事实的另一面:组也是网络账单的乘数。
  • 跨集群:主备各 10 GiB/天进出,一个月约 600 GiB 出网。

副本重建:平时不花、故障时一次性花

这笔账最容易被忽略,因为它不是月度成本,而是故障当天的一次性支出。

一台 broker 挂掉之后恢复,它上面的所有分区都要从 leader 全量拉回缺失的部分。规模是「这台机器承载的数据量」:

单机重建量 ≈ 集群总占用 ÷ broker 台数

60 GiB 的三节点集群,一台挂着要拉回约 20 GiB。这个传输是全速的,会和正常生产消费抢同一块网卡。卷一实测过 100 MiB 分区的追赶,5.2 秒完成;按同一带宽外推,20 GiB 需要十几分钟到几十分钟,取决于链路和磁盘。这段时间里:

  • 该 broker 上的分区不在 ISR 内,acks=all 的生产者会感受到延迟上升。
  • 如果同时有另一个 broker 抖动,min.insync.replicas=2 会拒写。
  • 网卡打满会影响同机房其他服务。

所以重建成本不只是「多花了带宽」,还是「故障恢复期间系统整体变慢」的机会成本。这部分在容量规划里要留出带宽头寸:正常峰值占用不要超过链路的 60%,给重建和追赶留余量。

人力:第三笔账,通常最大

前两笔能用公式算,这一笔不能,但它常常比前两笔加起来还大。

三档方案对运维的要求完全不同:

动作基础标准强化 + 跨集群
日常巡检看盘、看 lag看盘、看 lag、看 ISR以上 + 看复制延迟、看两端 lag 差
扩缩容加盘/加机加盘/加机 + 分区重分配以上 + 两端同时操作
版本升级停机窗口可接受滚动升级滚动 + 主备切换演练
故障演练偶尔定期定期 + 切换与回切
值班要求工作时间7×24 响应7×24 + 切换决策权限

「强化」方案多出来的不只是 450 GiB 存储和 600 GiB 月出网,还有一支会做主备切换决策的值班队伍。跨集群方案最贵的从来不是带宽,而是「切换一次要谁签字、切过去之后数据怎么回切」——这第九章讲最终一致性时提过,跨集群的最终一致性窗口是以分钟计的。

一个诚实的判断:如果一个团队没有能力定期演练切换,那跨集群方案买到的是心理安慰,不是可用性。 真出事时不敢切或者切了回不来,比没有灾备更糟。13.3 节讨论过 MirrorMaker2 的模型,那里说的是机制,这里说的是组织能力——两者缺一不可。

边际成本曲线:多花的每一份钱买到了什么

把三档按「防住的故障」排序,能看出投入的边际效率:

升级动作增加的量买到什么边际效率
RF 2 → 3+150 GiB(60 → 210)重建窗口内再坏一台不丢高,几乎必做
acks=1 → all网络 +0(复制本来就在跑)leader 挂时不丢未同步消息极高,纯配置改动
保留 3 → 7 天+120 GiB故障发现窗口从 3 天到 7 天中,取决于业务对账周期
保留 7 → 14 天+210 GiB再多一周的重放能力低,多数系统用不上
加跨集群+90 GiB 存储 + 600 GiB/月出网 + 值班机房级故障存活低到中,看监管与业务等级

这张表的读法是:先做边际效率高的,不要先做看起来最厉害的。

acks=1 → all 这一行的「增加量」是零,因为副本复制流量本来就在发生——acks=1 时 follower 也在拉,只是 leader 不等它们确认。改成 acks=all 不增加任何网络流量,只是让 leader 多等一会儿。它唯一的代价是延迟,不是带宽,不是存储。用延迟换「不丢」,这笔交易在绝大多数业务里都划算。

反过来,跨集群那一行的边际效率最低,但它常常是第一个被写上方案的东西——因为它最显眼。

不该省的钱

有三种省钱方式,事后看基本都是负收益:

砍保留期。 保留期是故障的处理窗口。把它从 7 天砍到 1 天,省 6 天的存储,但任何一次超过 24 小时才被发现的消费错误都变成「无法重放」。而消费错误被发现的时间,取决于业务对账周期,不取决于你的意愿。

砍副本。 RF=2 在单机故障时看起来「也扛住了」,容易得出「RF=2 够用」的结论。这个结论只在「重建期间不再坏」的前提下成立。而重建期间恰恰是集群最脆弱的时候——新 broker 刚回来,磁盘和网络都在满负荷。

砍 min.insync.replicas。 把它降到 1,等于告诉 broker「只要 leader 在就允许写」。这会让 acks=all 的保护失效:leader 一个人确认了,但它挂了之后这条消息可能不存在于任何副本上。4.2 节实测过 ISR 收缩时 NotEnoughReplicasException 的行为,那个异常的存在本身就是保护,为了「写入不要失败」而调低它,是把可靠性配置换成了可用性幻觉。

可以省的钱

同样有三笔,省了不心疼:

压缩。 11.2 节专门测了压缩算法的收益与负收益场景。文本类载荷压缩比常常能到 3 到 5 倍,直接乘在「每日数据量」这个乘数上,是唯一能同时降低存储、复制网络、重建量的手段。注意它降的是磁盘和网络,不降 CPU —— 生产端与 broker 端的 CPU 会上升,这笔账要一起算。

按 topic 分层。 上面所有算例都假设「全站一个等级」。真实系统里,审计日志可能需要保留 30 天且 RF=3,而某些缓存失效通知只需要保留 1 小时、RF=1、acks=1。把它们放进同一个等级,等于给低价值数据付高可靠性的钱。先给 topic 分类,再谈成本是性价比最高的一步。

清理没有消费者的 topic。 这类 topic 会一直积攒数据直到保留期,却没有任何产出。巡检里加上「无消费组且持续增长」这一条,往往能清出一批纯浪费。

换一个量级:日增 1 亿条

把假设放大 10 倍,看哪些成本线性和哪些不线性。日增 1 亿条、单条 1 KiB,每日 100 GiB:

项1000 万条/天1 亿条/天倍数
每日载荷10 GiB100 GiB10×
标准档存储(7 天 × RF3)210 GiB2100 GiB10×
复制流量20 GiB/天200 GiB/天10×
段尾余量与分区数相关不变1×
单机重建量(3 台)70 GiB700 GiB10×

两行「不变」和「10×」值得单独说。

段尾余量不随流量增长。 它由分区数乘平均段大小决定。这意味着在低流量场景里,如果分区开得很多,段尾余量可能超过数据本身的占比——一个日增 1 GiB、开了 500 个分区的集群,稳态数据只有几十 GiB,段尾却可能有上百 GiB。这类集群扩盘往往扩错了地方:真正吃盘的是空段,不是消息。

重建量线性增长,但可接受的重建时长不增长。 700 GiB 的单机重建,按 100 MiB 五秒的卷一实测外推,粗算需要接近十小时。这个时长需要和「RF=3 在重建期间再挂一台」的概率放在一起看:重建窗口越长,窗口内二次故障的概率越高。到某个规模,「加副本数」不再是选项,而是必须重新设计单机承载上限——把数据摊到更多 broker 上,让每次重建的搬运量降下来。

这也是为什么规模上去了以后不能只加盘:加盘增加的是单机承载,反而拉长重建时间,把可靠性往上加的副本数又抵消掉了。

压缩对三个乘数的影响

11.2 节测过压缩算法,这里只把结论折回成本公式。压缩改变的是「每日数据量」这一个乘数,但因为它出现在存储和网络两条链路的上游,实际影响是处处生效的。

假设文本类载荷压缩比 3:1,这个数字在 11.2 节的实测区间内(4 KB 事件、819 KB 样本上,lz4 是 2.58 倍、snappy 3.40 倍、zstd 5.69 倍、gzip 6.97 倍)。换算时按你选的算法取:

项不压缩3:1 压缩变化
每日载荷10 GiB3.3 GiB−67%
标准档存储210 GiB70 GiB−67%
复制流量20 GiB/天6.6 GiB/天−67%
单机重建量70 GiB23 GiB−67%
生产端 CPU基准上升正成本
broker 端 CPU基准上升(校验时解压)正成本

三个乘数里只有一个动了,但存储、复制、重建三项支出同时降了三分之二。这是性价比最高的单项优化——前提是载荷本身可压缩。已经压缩过的二进制(图片、protobuf 的某些字段、加密后的数据)压缩收益接近零甚至为负,11.2 节有实测对照。

要一起算的是 CPU:压缩把磁盘和网络的钱换成了 CPU 的钱。在 CPU 富余、磁盘和带宽紧张的集群里这笔换得很值;反过来就不值。

自建与托管的账怎么比

同一个「标准档」方案,自建和托管的成本结构完全不同:

成本项自建托管
存储硬件折旧 + 机房 + 电按 GB·月单价
网络带宽 + 交换机出网按 GiB 计费,内网通常含在单价里
运维人力全职或半职显著降低,但要保留对接与巡检
升级与故障自己承担厂商承担
弹性慢,要采购快,但要为弹性付溢价

本篇不给「哪个更便宜」的结论,因为变量太多:团队已有多少基础设施、消息量有多大、是否有合规要求必须自建。可以给的是三个判断维度:

  1. 量小的时候托管几乎总是更便宜,因为自建的人力成本不随量缩小,而托管单价随量缩小。
  2. 量大且稳定的时候自建可能更便宜,但省下来的是钱,付出的是「必须有人懂它」。
  3. 跨集群灾备在两种模式下都要单独付钱,托管不会替你做切换决策——这一条上面讲人力时说过。

无论选哪种,核算表里那张表都成立:量是同一批量,只是单价不同。

一张可填的核算表

把上面的口径收成一张表,你填自己的数字就行。前四列是你的输入,后三列是算出来的量:

项公式你的值单位
每日载荷日消息数 × 平均载荷10GiB/天
稳态存储每日载荷 × 1.05 × 保留天数 × 副本数210GiB
段尾余量分区数 × 平均段大小 ÷ 2待填GiB
复制流量每日载荷 × (RF − 1)20GiB/天
消费流量每日载荷 × 消费组数30(3 组)GiB/天
单次重建量稳态存储 ÷ broker 数70(3 台)GiB
跨集群出网每日载荷 × 2(进 + 出)20GiB/天

最后一行「跨集群出网」乘 2,是因为主集群要发出去、备集群要收进来,两端都可能计费,具体看你的计费口径。

填完之后做一件事:把「稳态存储 + 段尾余量」和「单次重建量」对一下。如果单次重建量超过了一个维护窗口内能搬完的量,说明你的 broker 单机承载过重,故障恢复时间会超出预期——这时候该做的是增加 broker 数量而不是加盘。

小结

  • 存储 = 每日数据量 × 保留天数 × 副本数,三个乘数对应三种不同的支出意图,不要混着谈。
  • 网络要按方向拆:生产、复制、消费、重建、跨集群,五种口径不同,其中重建是一次性大额支出,要留带宽头寸。
  • 人力常常比前两笔加起来大,跨集群方案尤其如此;没有演练能力时,灾备买到的是心理安慰。
  • 边际效率排序:acks=all 与 RF=3 先做,保留期按业务对账周期定,跨集群最后做。
  • 省钱优先省压缩与 topic 分级,不要省保留期、副本数和 min.insync.replicas。

本篇所有数字都是按公开配置语义推算的量,不是任何厂商的账单,也没有在本机跑过计费实验。你自己的单价乘上去,才是你的成本。


阅读导航:上一节 15.1 可靠性验收清单 | 下一节 15.3 libraryd-mq 卷二验收

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《Java 消息中间件·事件驱动架构》附录 B 架构决策记录模板
  2. 《Java 消息中间件·事件驱动架构》附录 A 术语表
  3. 《Java 消息中间件·事件驱动架构》15.3 全书验收