# 容量规划与性能诊断 > 「能扛多少」和「慢在哪」是两个问题,但都要用同一套语言回答:指标。拒绝用平均值讲故事,拒绝用扩容掩盖设计问题。 ## 一、先把指标定义清楚 | 指标 | 含义 | 常见误用 | | --- | --- | --- | | QPS / TPS | 每秒请求/事务数 | 用日均值掩盖峰值 | | 并发数 | 同时在处理的请求数 | 与 QPS 混淆 | | 延迟 P50/P99/P999 | 分位延迟 | 只看平均值,忽略长尾 | | 饱和度 | 资源使用率(CPU/内存/连接数/队列) | 只看 CPU,忽略连接池与磁盘队列 | | 错误率 | 失败请求占比 | 只统计 5xx,漏掉业务失败 | 高负载下真正决定体验的是**长尾**:P99 从 200ms 涨到 2s,平均值可能只动了几十毫秒。所以容量评估至少要看 P99 与错误率随负载的变化曲线,而不是一个数字。 ## 二、容量估算:先算,再压测 **利特尔法则**:并发数 L ≈ 到达率 λ × 平均停留时间 W。 举例:下单接口峰值 2000 QPS,单请求平均耗时 100ms,则理论上需要 200 个并发在处理。如果线程池只有 100,请求会排队,P99 直接爆炸。反过来,若线程池 500 而实际只需 200,多出来的线程只贡献上下文切换。 估算步骤: 1. 拿到**峰值 QPS**(不是日均,最好是大促实测或按业务倍数推); 2. 拿到**单请求耗时分布**(P50/P99); 3. 算出所需并发与连接数,再乘安全系数(1.5—2 倍); 4. 换算成资源:CPU、内存、连接池、下游依赖的配额; 5. **最后用压测验证**,而不是用压测代替估算。 ## 三、瓶颈定位:从宏观到微观 **第一层:链路上哪一段慢** 用分布式追踪看一次请求的时间都花在哪:应用计算、数据库、缓存、下游服务、网络。先把占比最大的那一段找出来,别一上来就优化代码。 **第二层:那一段里的哪个资源饱和** USE 方法:对每个资源看利用率(Utilization)、饱和度(Saturation,排队长度)、错误(Errors)。CPU 利用率 70% 但运行队列很长,说明在争抢;连接池利用率 100% 且有等待,说明池太小或下游慢。 **第三层:具体到函数** 火焰图看 CPU 热点;线程栈看阻塞点;GC 日志看停顿。注意区分**CPU 密集**与**等待型**瓶颈——后者加 CPU 没用。 ## 四、压测的四种类型 | 类型 | 目的 | 关注 | | --- | --- | --- | | 基准测试 | 单接口在理想条件下的极限 | 吞吐与延迟曲线拐点 | | 负载测试 | 目标峰值下是否稳定 | P99、错误率、资源水位 | | 浸泡测试 | 长时间运行是否退化 | 内存泄漏、连接泄漏、磁盘增长 | | 破坏性测试 | 依赖挂掉会怎样 | 降级是否生效、恢复时间 | 压测环境最容易骗人:数据量小(索引全在内存)、网络近(没有跨机房延迟)、依赖被 mock(下游没压力)。**结论只能用于相对比较,不能直接换算成生产容量。** ## 五、优化顺序(收益从高到低) 1. **减少工作量**:能不能不查、不传、不算?缓存结果、合并请求、去掉重复调用; 2. **缩短关键路径**:把串行改并行,把同步改异步(用户不需要立刻知道的事); 3. **缓存**:本地缓存 > 分布式缓存 > 数据库;注意失效策略与一致性代价; 4. **批量化**:N 次单条变 1 次批量,减少往返; 5. **扩容**:最后手段。扩容解决「量」的问题,解决不了「设计」的问题。 阿姆达尔定律提醒:如果串行部分占 20%,无论并行化剩下的 80%,整体最多快 5 倍。**先找串行段**。 ## 六、六个常见误区 1. 用平均值汇报性能,掩盖长尾; 2. 只看 CPU,忽略连接池、线程池、磁盘 IO、下游配额; 3. 压测不带真实数据量,索引全命中缓存; 4. 把「加机器」当优化,成本上去了根因还在; 5. 优化了不是瓶颈的地方(没有 profile 就动手); 6. 忽略 GC 与锁:CPU 不高但延迟高,往往是停顿或争抢。 ## 七、自检清单 - [ ] 我能说出核心接口的峰值 QPS、P99 与错误率基线 - [ ] 容量是按「峰值 × 安全系数」算的,不是按日均 - [ ] 我知道当前系统最饱和的资源是什么,水位多少 - [ ] 我做过浸泡测试,确认没有内存/连接泄漏 - [ ] 优化前有 profile 数据,优化后有对比实验 - [ ] 扩容前先确认瓶颈不在下游或数据库 - [ ] 压测结论标注了与生产环境的差异