性能不是一次突击
很多团队把性能当救火:上线前加机器、压一轮、修几个慢查询,上线后就不管了。等到流量涨上来,老问题又原样回来——因为瓶颈的根因从来没被真正找到过。
性能工程把这件事变成流程:先定可量化的目标,用压测让瓶颈现形,分层定位根因,再优化、再验证,最后把性能门禁做进流水线,让"快"不是一次状态,而是一条长期线。
没有目标的优化叫瞎调,没有验证的优化叫碰运气。
性能工程的四个步骤
- 定标延迟、吞吐、资源占用,先写下数字。
- 压测用压力把隐藏的瓶颈逼出来。
- 定位分层排查,找到真正的根因。
- 防护把性能门禁做进 CI,防止回潮。
先定目标:SLO 与性能预算
没有目标,就不知道优化到什么程度算完。目标要具体到数字:核心接口的延迟目标、系统能扛的并发数、允许的失败率。
| 维度 | 示例目标 | 衡量的理由 |
|---|---|---|
| 延迟 | 下单接口 P95 小于 300 毫秒 | 用户真实体验 |
| 吞吐 | 单机支撑 2000 请求每秒 | 容量是否够用 |
| 资源 | CPU 峰值低于 70% | 留出余量、防抖动 |
先有目标数字,压测结果才有比较的基准。
压测:让瓶颈现形
优化前先测量:用压测脚本逐步加压,同时记录延迟、吞吐和资源曲线。瓶颈通常藏在你没看的地方——连接池满了、锁竞争、慢查询、GC 停顿。
# 压测思路:逐步加压,观察拐点
1. 从低并发开始(如 50 并发)记录基线
2. 每轮翻倍加压(100, 200, 400…)
3. 记录每一轮的 P50 / P95 / 错误率 / CPU / 内存
4. 找到延迟开始陡增的那个点,那就是容量拐点
# 拐点之前系统健康,拐点之后排队开始,瓶颈通常藏在拐点前后
压测环境要尽量贴近生产:同样的数据量、同样的部署形态,否则压出来的结论不可信。
分层排查:从上到下过一遍
定位瓶颈要按层排查,每一层都有高频原因:
| 层 | 高频瓶颈 | 常用手段 |
|---|---|---|
| 应用代码 | 循环里的慢逻辑、序列化开销 | Profile、火焰图 |
| 数据库 | 慢查询、缺索引、锁等待 | 慢日志、执行计划 |
| 缓存 | 命中率低、热点失效 | 命中率指标、缓存击穿处理 |
| 网络与连接 | 连接池打满、超时重试风暴 | 连接池监控、熔断 |
先看数据,再动代码——别凭感觉改,用火焰图、慢日志把根因找实。
常见优化手法
根因确定之后,优化手法大多是这几类的组合:
- 缓存:把重复计算的昂贵结果缓存起来,注意失效策略与热点。
- 索引:给慢查询补上正确的索引,比在代码里打补丁更治本。
- 批量:把 N 次小请求合成一次批量请求,减少往返。
- 异步:非关键路径改异步,削峰填谷。
优化顺序有讲究:先治根因(慢查询、锁、循环),再谈缓存和批量的"加速",最后才是加机器。
把性能门禁做进 CI
优化完不防护,下个迭代又把性能改回去。性能门禁的做法是:在流水线里跑基准压测或关键路径性能测试,超标的变更直接卡住。
# 性能门禁的简化流程
1. 对核心接口定义基准性能用例(固定脚本、固定环境)
2. 每次合并前跑一轮迷你压测
3. 对比基线:P95 劣化超过 10% 则阻断合并
4. 人工确认是噪声还是真劣化,再决定放行
# 门禁不是为了拦人,是为了让性能劣化第一时间浮出水面
门禁让性能变成每次提交都要回答的问题,而不是上线前才想起的检查项。
快是一种长期状态
性能工程的终点不是某个接口优化了多少,而是系统形成了一整套防止变慢的机制。
- 目标可量化,优化有基准。
- 压测常态化,瓶颈早暴露。
- 排查有路径,根因不靠猜。
- 门禁进流水线,劣化早拦截。
当性能成为流程里的一部分,快就不是某一次的上限发挥,而是每次发布都默认成立的状态。