容量规划与成本估算
容量规划的输出不是一张机器清单,而是一条“业务指标 → 资源需求”的可复核推导链,加上每个环节的安全水位。写不出公式的地方就是拍脑袋的地方。IM 特有的三笔账必须分开算:连接吃内存、消息吃带宽与分区、历史消息吃存储与成本,用同一套手段去治三类瓶颈只会两头不讨好。
从业务指标到资源需求
业务输入:DAU、人均上行/下行条数、群规模分布、图片视频占比、消息保留天数、在线率曲线
↓
技术指标:峰值 CCU、上行 QPS、下行 QPS(含扇出)、存储日增、出口带宽、鉴权 QPS
↓
单机能力:压测标定的单机连接数、单机消息 QPS、单连接内存、单机网卡带宽(见 26 章)
↓
资源需求:实例数 = ceil(峰值指标 / (单机能力 × 0.7)),再按 N+1 或 N+2 加冗余
↓
成本估算:机器 + 带宽 + 存储 + 中间件 + 推送 + 可观测,整体乘冗余系数
单机能力那一格必须来自压测的拐点而不是规格书:8C16G 的接入进程可能撑 8 万连接,也可能只撑 4 万,差别在协议开销、TLS 与写缓冲策略上。
第一笔账:连接内存
| 组成部分 | 单连接典型开销 | 说明 |
|---|---|---|
| 读缓冲 | 1~4 KB | 按最大帧长设,设成 64KB 是纯浪费 |
| 写缓冲 | 1~4 KB | 慢客户端出现时才会放大,必须有上限 |
| 连接对象与会话结构 | 1~2 KB | 含 uid、设备、心跳时间、订阅关系 |
| 协程或线程栈 | 4~8 KB | Go 初始栈 8KB 起,按需增长 |
| TLS 会话状态 | 8~16 KB | 公网长连接基本都要开,这笔开销不能省 |
| 路由表一侧 | 50~100 B | 存在 Redis 里,只算 Redis 内存 |
单连接内存 ≈ 读缓冲 + 写缓冲 + 连接对象 + 协程栈 + TLS 状态 ≈ 20 ~ 50 KB
堆内存 = 连接数 × 单连接内存 8 万 × 32KB ≈ 2.5 GB
容器内存 = 堆内存 × (1 + 冗余系数 0.5) ≈ 3.8 GB → 8 万连接的接入进程按 4~6 GB 规划
Redis 路由 = 在线连接数 × 100B 300 万连接 ≈ 300 MB,用集群分区承载
- 慢客户端是内存杀手:写缓冲必须设硬上限(如 256KB),超限直接断开并记录;否则一个只连不读的客户端能把单机内存吃掉几十 MB。
- 断连后内存不立刻归还是 Go 的正常行为(惰性归还),规划时按峰值而非稳态估算,压测时看
heap_released而不是 RSS。
第二笔账:带宽
上行带宽 = 上行 QPS × 单条上行字节 × (1 + 协议头占比 0.1)
下行带宽 = 上行 QPS × 平均扇出 × 单条下行字节
心跳带宽 = 连接数 × 心跳帧字节 / 心跳间隔秒数
出口峰值 = max(上行, 下行) × (1 + 重传与握手开销 0.15)
上行:5 万条/s × 300B ≈ 15 MB/s ≈ 120 Mbps
下行:平均扇出取 3 → 15 万条/s × 300B ≈ 45 MB/s ≈ 360 Mbps
心跳:300 万连接 × 24B / 30s ≈ 2.4 MB/s ≈ 19 Mbps
建连风暴:1 万次/s 建连 × TLS 握手约 6KB ≈ 60 MB/s ≈ 480 Mbps(持续几秒)
扇出是乘数不是加数:同一批消息发给 500 人群,下行直接放大 500 倍。容量表里必须把“平均扇出”和“最大扇出”分开列,只填平均值会在一个热点群里全线超限。心跳本身可以忽略,真正的带宽与 CPU 消耗在 TLS 握手和断线重连上(01 章已给过这个结论,压缩成本时要往这里看)。
第三笔账:存储
日增行数 = DAU × 人均上行条数 × (1 + 需要单独落库的收件箱比例)
存储字节 = 日增行数 × 单条落库字节 × (1 + 索引与冗余系数 0.3)
保留总量 = 存储字节 × 保留天数 × 副本数
| 项目 | 取值 | 说明 |
|---|---|---|
| DAU | 1000 万 | 业务给定 |
| 人均上行 | 40 条/日 | 业务给定 |
| 落库行数 | 4 亿行/日 | 群聊按读扩散只落一份,写扩散会乘群人数 |
| 单条落库字节 | 500 B | 正文 + 发送者 + 会话 + 时间戳,含索引开销 |
| 日增存储 | 约 260 GB/日 | 4 亿 × 500B × 1.3 ≈ 260 GB,按 300 GB 预留 |
| 保留 180 天 | 约 54 TB(单副本) | 三副本约 160 TB,这是成本的主要来源 |
| 离线消息 | 近 7 天热点 | 热数据放 Redis/SSD,冷数据归档 |
成本优化按收益排序:图片视频只存 URL 走对象存储与 CDN(占字节数的大头,最有效)→ 冷数据列式压缩归档(常见 3~5 倍压缩比)→ 离线队列设 TTL → 按会话活跃度分级保留(活跃会话 180 天、沉默会话 30 天)→ 历史消息归档到低成本存储并按需回捞。
资源换算与冗余
实例数 = ceil(峰值指标 / (单机能力 × 0.7))
接入层:N+2(发布与单机故障都会触发连接重建,重建本身还有风暴)
逻辑层:N+1(无状态,扩容快,冗余可以小)
路由层:按分片固定(如 8 分片 × 3 副本),不随流量线性扩,扩的是分片数
跨 AZ:每层至少 2 个可用区,单 AZ 故障时另一区要能承接 100% 流量
即每个 AZ 都按 50% 负载部署 → 机器数实际上要接近翻倍
- 跨 AZ 双倍部署是 IM 成本的隐形大头,但省不得:单 AZ 只留 30% 余量的方案,在真正掉一个区时会因为重连风暴彻底崩掉。
- 定容用日曲线的 P99 峰值(跨年零点这类尖峰)而不是平均峰值,并把峰值系数写进文档,否则同一份数据会被不同的人算出十倍差距。
成本估算表模板
| 项目 | 计费口径 | 数量 | 单价(填你所在云的公开价目表) | 月成本 |
|---|---|---|---|---|
| 接入层机器 | 8C16G 包年或按量 | 50 台(含冗余) | 待填 | |
| 逻辑层机器 | 4C8G | 20 台 | 待填 | |
| Redis 集群 | 内存规格 | 512 GB | 待填 | |
| MySQL | 规格 × 分片数 | 16 分片 × 3 副本 | 待填 | |
| 对象存储 | GB/月 | 54 TB | 待填 | |
| 公网带宽 | 按 95 峰值计费 | 500 Mbps | 待填 | |
| 厂商推送 | 按调用量 | 按离线用户数估 | 待填 | |
| 可观测 | 日志存储 + 采样流量 | 按采样率估 | 待填 |
单价一栏请填你自己云的公开价目表数值:不同云、不同付款方式(包年包月 vs 按量)差异可达 3 倍以上,任何写死的具体报价都会过期,只保留口径与公式才可复核。
分片与扩容预留
- 分片数一次规划到 3 年:按 uid 哈希 16 库 × 64 表,给扩容留 4 倍空间;提前分片远比后期在线迁移便宜。
- 连接容量与存储容量分开规划:接入层能无脑加机器,路由表与会话表的扩容需要数据迁移窗口,节奏完全不同。
- 留一条“降级后仍能服务”的底线容量:按关闭离线推送、只保在线用户、整体限流 40% 后的量级准备资源(见 28 章)。
- 每次容量评审后回填实际值:预估 8 万连接、实际 5.5 万,就把单机能力改成 5.5 万,下一次估算才有基准。
容量规划表模板
| 模块 | 峰值指标 | 单机能力 | 实例数 | 冗余 | 日增数据 | 保留期 | 备注 |
|---|---|---|---|---|---|---|---|
| 接入层 | 400 万 CCU | 8 万连接 | 50 | +2 | 无 | - | 跨 2 个可用区 |
| 逻辑层 | 5 万条/s | 8000 条/s | 9 | +1 | 无 | - | 无状态,看重下游 |
| 路由层 | 400 万 key | 按分片 | 8 分片 | 3 副本 | 300 MB | 会话期 | Redis 集群 |
| 消息库 | 4 亿行/日 | 单表 3000 万行 | 16×64 | 3 副本 | 260 GB/日 | 180 天 | 冷热分离 |
| 离线队列 | 300 万用户 | 按内存 | 512 GB | 1 副本 | 按 TTL 回收 | 7 天 | 只存位点与摘要 |
填完这张表,容量评审的争论点会从“感觉不够”变成“哪个数字需要修正”,这就是容量规划最大的价值。
小结:容量规划把业务输入翻译成 CCU、上行 QPS、带宽与存储日增,再用压测标定的单机能力除以 0.7 的安全水位换出实例数;三笔账分别对应连接内存(按 20~50 KB/连接估)、带宽(按字节数× 扇出,不是消息条数)与存储(日活 × 人均条数 × 单条字节 × 保留天数 × 副本数);成本大头在存储与公网带宽,优化优先级是媒体外链、冷数据压缩归档、离线数据 TTL 与分级保留,最后把结论固化进容量规划表并定期回填实际值。