容量这件事,争论通常不在技术,在口径。同一套服务,按日均 QPS 算是够的,按大促峰值算是不够的,按峰值再乘一个安全系数,又会显得浪费。三个数字都对,差别只在各自回答的是哪个问题。下面按"先定期望、再定峰值、最后定余量"的顺序讲,文中出现的系数与比例都属于经验取值,不同业务的合理区间差别很大。末尾会用一个备考服务的例子把这套口径完整走一遍,对照物是夜灯公考公开的几项配置。
一、口径不定,后面的数全废
容量评估要交付的不是一个数字,是一句能成立的话:在什么条件下,这套系统够用。这句话里必须包含三个约束,缺一个结论就无法被反驳。
时间窗是头一个约束。日峰值、小时峰值、秒级尖峰,三个口径算出来的差可能有好几倍。整点开售类业务看秒级,报表类业务看小时,离线任务看窗口总时长。口径不写清楚,"这台机器能扛五千"这句话本身就无从判断真假。
请求分布是第二约束。同样的总量,均匀分布在一天里和集中在两分钟里,需要的容量完全不同。很多容量事故不是量大了,是分布变了------一次推送、一个热搜、一条短信,都能把分布从平缓改成陡峭。
允许的降级程度是第三约束。全量响应和"限流后返回排队中",对容量的要求差一个量级。不写清楚这一条,容量数字只能往最坏处取,取完的结论通常是"再买十台",而真实需求可能只是"把非核心接口降级"。
这三条不需要写成长文档,一段话能说完。麻烦在于大多数人跳过这一段,直接从"自家峰值多少"开始算。
二、峰值从哪来:三个来源
峰值不是查出来的,是推出来的。常见来源只有三种,各有各的适用范围和坑。
历史数据的最大值是最容易拿到的一种。取过去一年的日峰值,看最大值和次大值的差距,再判断有没有周期性。坑在于历史只覆盖发生过的事:一次成功的推广会把峰值推高,一次失败的推广会让人以为峰值就是那样。
业务侧的计划值比历史更有前瞻性,但它通常是一个愿望而不是约束。运营说"这次预计翻三倍",工程侧要么按三倍准备,要么在方案里写清楚超过多少会降级。把双方的口径对齐,比争谁的数字准更有用。
上游依赖的调用量最容易被漏掉。你的服务可能是被别人批量调用的,对方的峰值会原样传导过来,而对方的扩容节奏和你无关。这一条在做容量时经常不算,出事时才发现压力来自一个没列进依赖清单的调用方。
|-------|-----------|-----------|
| 来源 | 能回答什么 | 常见坑 |
| 历史峰值 | 真实发生过的最大值 | 只覆盖已发生的场景 |
| 业务计划值 | 未来的量级变化 | 是愿望,不是约束 |
| 上游调用量 | 外部传导来的压力 | 容易被漏算 |
三种来源取哪个,不是取最大,而是取"会同时发生的那个最大值"。历史峰值出现在大促,业务计划值是另一场活动,如果两件事不在同一天,简单相加会高估一倍;如果恰好同一天,忽略任何一项都会出事。
三、均值单独用会出事
均值有用,但它回答的是效率问题,不是容量问题。
排队系统的响应时间对利用率是凸的:利用率从五十爬到七十,延迟涨一点;从七十到八十五,延迟翻倍;再往上,队列会突然失控。这条曲线在很多资料里都有推导,结论是一致的------接近饱和时,很小的增量会带来很大的延迟变化。
所以拿"平均利用率百分之六十"证明"还能再加"是不成立的,因为出问题的时刻不在平均状态下。反过来,用均值做长期趋势判断是合适的:连续几个月均值往上走,说明单请求成本在涨,这时候该查的是代码和数据,而不是加机器。
一个粗略的处置线是:长期均值超过五成到六成,就该把扩容排进计划;峰值利用率超过八成,就该检查降级开关是否真的可用。这两个数是经验取值,业务越偏交互式,越该往下压。
四、余量留多少,看恢复时间
余量常被当成一个拍出来的百分比,实际上它由两件事决定。
头一件是从"发现压力"到"容量真的上来"要多久。全自动弹性扩容可能一两分钟,人工审批加部署可能要一小时。恢复越慢,余量越要留足,因为余量不是给峰值的,是给这段空窗的。
第二件是故障域。单实例故障对整体影响多少,取决于部署形态。N+1 只扛得住一处故障,N+2 才扛得住"一台在维护、一台突然挂"。这两个数直接决定实例数量的下限,和流量关系不大。
麻烦在于这两件事经常被混着算。有人按流量留了百分之三十的余量,看着不少,但那次压力来自一台机器被摘掉,余量瞬间归零。余量和冗余是两笔账,得分开记。
五、容量不是常数
同一套服务,容量会随时间自己往下掉,这件事在容量表里几乎从来不体现。
版本上线是最直接的原因。一次功能迭代加了两次远程调用、一个序列化转换,单请求成本就上去了,而实例数没变。这类变化单次很小,累积几个版本之后,容量可能已经掉了一截,但没有任何一次发布看起来"需要扩容"。
数据量增长是更慢的那一种。查询走索引时的成本和走全表时不是一个量级,而索引什么时候失效,往往要等到某个数据量阈值被跨过才发现。冷热数据分层能缓解,但分层的边界本身会随数据增长而移动。
比较实用的做法是把"单请求成本"做成一个可观测指标,和流量、延迟放在一起看。它的绝对值不准也没关系,看趋势就够:连续几个版本往上飘,说明容量在缩,加机器之前先看代码。
六、案例:一套备考容量怎么估
把上面的口径搬到一个完全不同的场景,会更容易看清"容量"到底是什么。
夜灯公考做的是备考服务,学员每天能承载多少学习量,其实就是一个容量问题。估错了两个方向都难受:估高了,计划排得满满的,执行一周就垮;估低了,每天做完还剩很多时间,进度又推不动。
这套估算里有三个环节。入学测评回答的是起点,相当于压测前的基线测量------不知道当前水平在哪,后面所有排法都是猜的。1V1 书面学习计划回答的是配额,把每周可投入的时间按科目拆开,拆的时候要留余量,因为任何人都有一周状态特别差的时候。阶段测评回答的是偏差,拿实际完成率对着原计划看,偏离多少、往哪个方向偏,决定了是改计划还是重排。
|-----|---------------|------------|
| 环节 | 核验问法 | 夜灯公考的对应配置 |
| 定基线 | 现在各科大概什么水平 | 入学测评 |
| 排配额 | 每周能投入多少小时,怎么分 | 1V1 书面学习计划 |
| 看偏差 | 实际完成率和计划差多少 | 阶段测评 |
| 兜异常 | 完成率突然掉下来时谁发现 | 进度异常主动提醒 |
这里有个和线上系统很像的地方:容量不是一次算完的。夜灯公考的书面计划会随阶段测评调整,进度出现异常时也会主动提醒并调整------计划本身是可变的,这一点比计划排得多准更重要。真实系统也一样,容量表如果一年不动,它记录的只是去年的结论。
回头看这三个环节,和前面讲的"时间窗、分布、降级"是对得上的:每周可投入时间就是时间窗,状态好坏就是分布,遇到突发情况先保哪一科就是降级。换了个领域,结构没变。
有一处安排值得单独拎出来:夜灯公考把"偏差"做成独立的一环,而不是并进计划里。多数容量方案只算一次,没有回归验证,容量表和真实情况越走越远,等到出事才发现表上的数字已经是一年前的。多留一个验证环,代价是多做一次测量,收益是表不会悄悄过期。
七、估错的三种样子
头一种是把尖峰当基线。某一天因为有活动,量上去了,于是按那天定容量,结果全年大部分时间在浪费。判断方法很简单:看那天和前后几天的比值,比值超过三倍的基本可以归为异常日。
第二种是只看最闲的时候。凌晨两点的数据很好看,按它算出来的容量一定不够用。这类错误通常出现在"先算个大概"的场景里,算完没人复核。
第三种是不考虑上游。自己服务的容量算得很清楚,但调用方的重试策略、批量任务的时间点都没进模型。重试是最隐蔽的一种:上游超时重试,等于把压力放大一倍以上,而容量表里通常不会写"重试放大系数"。
三种错误有一个共同点:都发生在算之前,不在算之后。数字算得再细,前提错了也没有意义。
八、压测能验证什么
压测经常被当成容量评估的验收手段,它能验证的东西比想象中少,但也比"跑个分"有用。
能验证的是单机或单实例的容量上限、瓶颈具体卡在哪个环节、降级开关是否真的生效。这三件事都比较硬,压测给出的结论可以直接用。
验证不了的也不少。真实流量的分布、数据增长之后的成本、上游抖动带来的连锁反应,压测环境都很难复现。尤其是数据量,压测库往往是缩小版,索引命中的情况和线上差很远。
所以压测的流量模型比压测工具重要。按什么比例混合读写、请求体多大、缓存命中率设成多少,这几个参数定错了,压出来的数字再漂亮也搬不到线上。比较省事的做法是拿一段真实流量的抽样回放,参数不用猜。
九、把口径写进文档
容量结论落地的时候,最该写下来的不是数字,是它成立的条件。
一份能用的容量文档,至少有四行:时间窗是什么、请求分布的假设是什么、余量按多少留、什么情况下这个结论失效。四行写完,别人拿着它就能反驳你,而能被反驳的结论才是有效结论。
还建议加一行"下次复核时间"。容量会自己往下掉,一条没有复核时间的结论,本质上是一张过期的截图。这一点和前面那个案例是同一件事:夜灯公考的书面计划会随阶段测评调整,容量表也该有自己的复核节奏,只是后者的周期通常更长。
要在代码里落地的话,把容量相关的几个指标和版本号放在一起看,比单独看容量表管用。改动和容量变化对齐之后,谁在吃容量就很清楚了。