分布式系统建成之后,账单会成为第二个「性能问题」:它同样会随规模非线性增长,同样在事故前没人注意,同样需要指标、预算与治理流程。区别在于性能问题会以超时和报错的形式主动找你,而成本问题只会安静地出现在月度账单上。
成本治理的难点不是「不知道能省」,而是不知道该省哪里、省了会不会影响稳定性、省下来的钱算谁的。本文按「成本结构 -> 单位成本指标 -> 计算/存储/网络三条主线 -> 组织流程」的顺序,给出一套可执行的治理方法。
一句话:成本优化的第一原则是先建立单位成本指标(每单/每请求/每用户的成本),否则所有优化都是盲目的。
1. 成本从哪里来
1.1 成本结构
| 类别 | 典型占比 | 主要驱动 |
|---|---|---|
| 计算 | 40% ~ 60% | 实例数量、规格、使用率 |
| 存储 | 15% ~ 30% | 数据量、副本数、存储类型 |
| 网络 | 10% ~ 25% | 跨区流量、出口流量、CDN 回源 |
| 托管服务 | 10% ~ 30% | 数据库、消息队列、可观测性 |
| 许可与商业软件 | 视情况 | 商业数据库、监控平台 |
这张表的作用是排序优先级:如果计算占 60%,花大力气优化网络出口流量就是舍本逐末。多数团队的第一个动作应该是拉出账单按维度(服务、环境、可用区)拆分,而不是直接开始调参数。
1.2 单位成本指标
绝对成本没有意义——业务增长 50% 成本增长 30% 其实是优化,成本不变业务萎缩 20% 反而是恶化。因此必须建立单位成本指标:
单位成本 = 月度总成本 / 业务量指标
常见口径:
电商 -> 每笔订单成本(cost per order)
社交 -> 每 DAU 成本(cost per DAU)
API -> 每百万请求成本(cost per 1M requests)
存储 -> 每 TB 月成本(cost per TB-month)
推理 -> 每千 token 成本(cost per 1K tokens)
# 单位成本看板的核心计算
def unit_cost(total_cost, business_volume):
if business_volume == 0:
return float("inf") # 业务量为 0 时单位成本无意义,需单独告警
return total_cost / business_volume
# 判断优化是否有效:看单位成本的趋势,而不是绝对成本
# 单位成本下降 = 规模效应生效;单位成本上升 = 存在非线性膨胀
单位成本上升通常指向三类问题:数据膨胀(存储随业务量超线性增长)、N+1 放大(一次业务操作触发多次内部调用)、僵尸资源(无人使用的实例与索引)。这三类是成本治理的主要战场。
2. 容量治理
2.1 容量水位
容量治理的第一件事是给所有资源定水位线,而不是让资源自由增长:
| 资源 | 目标水位 | 告警阈值 | 说明 |
|---|---|---|---|
| CPU(在线服务) | 30% ~ 50% | 70% | 太低是浪费,太高无突发余量 |
| 内存(在线服务) | 50% ~ 70% | 85% | 需留 GC 与突发空间 |
| 数据库连接池 | 40% ~ 60% | 80% | 超过 80% 时排队延迟陡增 |
| 磁盘使用率 | < 70% | 85% | 超过 85% 写入性能下降 |
| 消息队列积压 | 接近 0 | 持续增长 | 积压即消费能力不足 |
CPU 长期低于 30% 是明确的浪费信号,通常意味着实例规格过大或副本数过多。但要注意:在线服务的低水位可能是刻意的突发余量,压缩前必须先确认流量模型(是否有明显峰谷)。
2.2 弹性伸缩
弹性伸缩的收益取决于流量模型与缩容速度:
伸缩三要素
1. 指标:CPU / QPS / 队列深度(选与负载线性相关的那个)
2. 阈值与冷却:扩容要快(1 分钟内),缩容要慢(10~30 分钟)
3. 边界:min 副本数保证基线可用,max 副本数防失控
常见错误
- 用 CPU 做指标但服务是 IO 密集 -> CPU 不涨,永不扩容
- 缩容冷却太短 -> 抖动式扩缩,反复创建销毁
- max 设置过大 -> 一次流量异常拉起大量实例,账单暴增
缩容比扩容难:缩容太快会把还没处理完的请求掐断,缩容太慢则夜间低峰期白付钱。实践做法是给缩容加「延迟窗口」——只有当指标持续低于阈值 15 分钟以上才缩,并优先缩最新创建的实例。
2.3 混部与资源隔离
混部(Colocation)把在线服务与离线任务部署在同一批机器上,用离线任务填满在线服务的空闲资源,理论上可把整体利用率从 20% 提到 50% 以上。代价是隔离复杂度:
| 隔离维度 | 手段 | 风险 |
|---|---|---|
| CPU | cgroup 权重 + 绑核 | 离线任务抢占导致在线 P99 抖动 |
| 内存 | cgroup 上限 + 内存回收 | 离线任务触发全局内存压力 |
| 磁盘 IO | IO 权重 + 限速 | 离线任务打满 IO,在线服务变慢 |
| 网络 | 带宽限速 + QoS | 离线任务占满带宽 |
混部的前提是有可靠的干扰检测与快速驱逐能力:一旦检测到在线服务的 P99 因离线任务劣化,必须能在秒级驱逐离线任务。没有这个能力的混部,等于把稳定性押在离线任务的「自觉」上。
3. 计算成本优化
3.1 实例选型
| 策略 | 收益 | 适用 |
|---|---|---|
| 按实际负载选规格 | 10% ~ 30% | 通用,先做 |
| 抢占式/竞价实例 | 60% ~ 90% | 无状态、可中断任务 |
| 预留实例/节省计划 | 30% ~ 50% | 稳态基线负载 |
| 架构升级(ARM) | 20% ~ 40% | 编译型语言、无 x86 依赖 |
| 降配 | 20% ~ 50% | 水位长期过低的实例 |
组合使用是常态:稳态基线用预留实例,峰值用按需,离线批任务用竞价实例。把这三层对应到流量曲线上,通常能拿到 40% 以上的降幅。
3.2 请求级降本
单机利用率提升有天花板,真正的大头往往在请求路径本身:
请求级优化的三类手段
1. 减少请求数
- 合并接口(一次返回多个资源,消除 N+1)
- 前端聚合(BFF 层合并下游调用)
2. 减少单请求成本
- 加缓存(命中率每提升 10%,后端压力降约 10%)
- 减少序列化开销(换更紧凑的协议)
3. 削峰
- 请求排队 + 平滑消费
- 把同步调用改异步,用批处理摊薄成本
缓存是性价比最高的一环,命中率与后端压力几乎线性相关,分层缓存与失效策略的设计见 https://plumephp.com/distributed-cache-strategies/。批处理则适合日志、指标、离线计算这类可延迟的场景:把「来一条处理一条」改成「攒一批处理一批」,固定开销被摊薄,单位成本往往能降一半以上。
不过批处理有明确的适用边界:延迟敏感的业务不能批(用户等不了 5 分钟),有严格顺序要求的数据不能批(批内重排会破坏顺序)。判断标准是「业务能容忍多大的处理延迟」,能容忍 1 分钟以上才值得批。
3.3 Serverless 的账单陷阱
Serverless 按调用次数与执行时长计费,看起来省钱,但两类场景会反噬:
| 场景 | 问题 | 结果 |
|---|---|---|
| 高频短请求 | 每次调用有固定冷启动/调度开销 | 单位成本高于常驻实例 |
| 长连接/长任务 | 按时长计费且无法复用 | 成本随连接数线性上升 |
| 内存配置过大 | 计费与内存成正比 | 为省 CPU 调大内存反而更贵 |
| 无并发上限 | 流量突增触发大量并发实例 | 单小时账单暴增 |
Serverless 的适用边界与成本模型,见 https://plumephp.com/distributed-serverless-architecture/。判断标准很简单:如果请求频率高且延迟敏感,常驻实例几乎总是更便宜;Serverless 的优势在于低频、突发、事件驱动的场景。
4. 存储成本优化
4.1 存储分层
| 层 | 介质 | 单位成本 | 访问延迟 | 适用 |
|---|---|---|---|---|
| 热 | SSD / 内存 | 高 | 微秒~毫秒 | 近期活跃数据 |
| 温 | 标准云盘 | 中 | 毫秒~十毫秒 | 偶发访问 |
| 冷 | 对象存储标准 | 低 | 十毫秒~百毫秒 | 归档、备份 |
| 冰 | 归档存储 | 极低 | 分钟~小时 | 合规留存 |
分层的关键是自动化的数据生命周期策略,而不是靠人工搬数据:
# 对象存储生命周期规则示例
lifecycle:
- prefix: logs/
transitions:
- { day: 30, storage_class: STANDARD_IA } # 30 天后转温
- { day: 90, storage_class: GLACIER } # 90 天后转冷
- { day: 365, storage_class: DEEP_ARCHIVE } # 1 年后转冰
expiration:
day: 1095 # 3 年后删除
4.2 数据生命周期
存储成本失控的第一大原因是只写不删:
| 数据 | 常见问题 | 治理动作 |
|---|---|---|
| 应用日志 | 全量长期保留 | 分级保留,超过 30 天转冷 |
| 监控指标 | 高精度永不降采样 | 按时间降采样(1s -> 1m -> 1h) |
| 数据库历史表 | 无归档策略 | 按时间分区 + 定期归档 |
| 备份快照 | 快照链无限增长 | 保留最近 N 个 + 月度全备 |
| 消息队列 | 消费完不删除 | 设置 TTL 与保留上限 |
监控指标的降采样收益尤其显著:把 1 秒精度保留 7 天、1 分钟精度保留 90 天,通常能砍掉 80% 以上的指标存储成本。
4.3 副本数与压缩
副本数决策
3 副本:默认,容忍 1 节点故障
2 副本:成本降 33%,但容错性下降,需评估可接受性
EC(纠删码):1.5 倍冗余即可达到 3 副本的可靠性
代价是恢复时带宽与 CPU 开销大
压缩决策
对日志、JSON、时序数据:压缩率常达 5:1 ~ 10:1
代价:写入与查询时的 CPU 开销
判断:如果存储成本 > 压缩 CPU 成本,就该压
纠删码(Erasure Coding)是对象存储与冷数据的标准做法,它把冗余从「整份复制」改成「分片 + 校验块」,1.5 倍空间即可达到多副本的可靠性。代价是恢复一个分片需要读取多个其他分片,恢复期间的网络开销远高于副本复制。
5. 网络成本
5.1 跨区流量
云厂商对内网跨可用区、跨地域流量均计费,且跨地域单价通常是同区内的数倍:
流量成本排序(从低到高)
同可用区 免费或极低
同地域跨可用区 低(但量大时不可忽视)
跨地域 高(常为同区内的 5 ~ 10 倍)
公网出口 最高
降本手段
1. 让计算靠近数据:把服务部署在数据所在可用区
2. 减少跨区调用:批量合并、结果缓存
3. 压缩传输:gRPC + protobuf 优于 JSON
4. 拓扑感知路由:优先同区副本,故障时才跨区
第 4 条尤其有效:很多分布式数据库默认在副本间随机读,配置成「优先读同区副本」后,跨区流量能降一个数量级。跨集群与跨云的拓扑设计,见 https://plumephp.com/distributed-multi-cluster-cross-cloud/。
5.2 CDN 与回源
CDN 的成本结构是「CDN 流量单价 + 回源流量」:
| 优化点 | 手段 | 收益 |
|---|---|---|
| 提升缓存命中率 | 合理设置 Cache-Control、去除无意义查询参数 | 回源流量显著下降 |
| 减少回源次数 | 合并静态资源、使用长缓存文件名 | 回源 QPS 下降 |
| 分级缓存 | 边缘 -> 中间层 -> 源站 | 源站压力下降 |
| 压缩与图片处理 | 自动 WebP/AVIF、按需裁剪 | 传输体积下降 30%+ |
缓存命中率是 CDN 成本的核心杠杆:命中率从 80% 提到 95%,回源流量直接减少 75%。
6. 组织与流程
6.1 成本归属
技术手段之外,成本归属(Cost Attribution) 是能否持续降本的决定因素:
归属三原则
1. 每笔成本都能打到具体团队(标签:team / service / env / owner)
2. 共享资源按可解释的规则分摊(按请求量、按 CPU 使用)
3. 每个团队能看到自己的单位成本趋势
反模式
- 所有资源无标签 -> 只能看到总账,无法归因
- 共享资源按人头分摊 -> 用得多的团队没有压力
- 只看总成本不看单位成本 -> 业务增长时误判为恶化
标签体系是基础工程。没有标签,成本优化只能靠「猜哪个服务贵」。FinOps 的完整方法论与组织实践可延伸阅读数据 FinOps 成本优化 。
6.2 预算与告警
| 告警类型 | 触发条件 | 动作 |
|---|---|---|
| 预算告警 | 月度消耗达预算 80% | 通知负责人 |
| 异常增长 | 日环比增长 > 30% | 立即排查(常是配置错误或流量异常) |
| 僵尸资源 | 实例 CPU < 5% 持续 7 天 | 自动标记待回收 |
| 单位成本恶化 | 单位成本连续 3 周上升 | 进入专项治理 |
「异常增长」告警的价值最高:成本突然跳涨往往不是业务增长,而是某个配置错误(如缓存失效导致全量回源)或流量异常(如爬虫)。这类问题的排查时间窗口很短,早一天发现就少一天损失。
6.3 降本与稳定性的权衡边界
降本必须有明确的禁区,否则会以稳定性为代价换取短期账面收益:
| 动作 | 省下的钱 | 潜在代价 | 是否可做 |
|---|---|---|---|
| 缩减副本数(3 -> 2) | 33% 存储 | 容错能力下降 | 需评估,通常不可 |
| 关闭跨区容灾 | 跨区流量与冗余资源 | 机房故障即全站不可用 | 不可 |
| 提高水位到 80% | 20% ~ 30% 计算 | 突发流量即雪崩 | 需保留突发余量 |
| 延长缓存 TTL | 后端压力下降 | 数据陈旧 | 视业务可容忍度 |
| 降低日志级别 | 存储与采集成本 | 排障信息缺失 | 可做,但保留错误日志 |
| 缩短监控保留期 | 监控存储成本 | 无法回溯历史问题 | 可做,需保留降采样数据 |
判断标准是一句话:这项优化是否降低了系统吸收故障的能力? 如果是,它就不是成本优化,而是风险转移——省下的钱迟早会以事故的形式还回去。
7. 常见坑
| 坑 | 后果 | 修法 |
|---|---|---|
| 只看总成本不看单位成本 | 业务增长被误判为成本恶化 | 建立单位成本指标并看趋势 |
| 无标签直接优化 | 不知道省在哪里,无法归因 | 先做成本归属再动手 |
| 过度压缩水位 | 一次流量高峰即雪崩 | 保留突发余量,按流量模型定水位 |
| 混部无干扰检测 | 离线任务拖垮在线服务 | 上混部前先建干扰检测与驱逐 |
| 存储只写不删 | 存储成本线性增长 | 生命周期策略 + 降采样 |
| 忽略跨区流量 | 网络账单占比超预期 | 拓扑感知路由 + 结果缓存 |
| 为省钱砍冗余 | 可用性下降,事故成本远超节省 | 冗余是底线,不参与成本优化 |
最后一条是原则性的:成本优化不能突破可用性底线。单副本省钱、跨区容灾砍掉、备份只留一份,这些「优化」在事故发生时造成的损失通常远超省下的钱。
总结
| 主题 | 关键内容 |
|---|---|
| 成本结构 | 计算、存储、网络、托管服务四类,先排序再动手 |
| 单位成本 | 每单/每 DAU/每百万请求成本,看趋势而非绝对值 |
| 容量治理 | 水位线、弹性伸缩(快扩慢缩)、混部需干扰检测 |
| 计算降本 | 实例分层(预留+按需+竞价)、请求级合并与缓存 |
| 存储降本 | 分层存储、生命周期、降采样、纠删码 |
| 网络降本 | 拓扑感知路由、减少跨区、提升 CDN 命中率 |
| 组织保障 | 成本归属标签、预算与异常增长告警 |
成本治理与性能治理在方法上高度相似:先度量、再归因、后优化、最后固化到流程。区别在于成本治理天然是跨团队的——它需要财务、平台、业务三方协作,因此组织手段(标签、归属、预算)往往比技术手段更决定成败。把单位成本指标接进日常看板,让每个团队看到自己服务的成本趋势,比任何一次专项优化都更持久。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。