跳转到内容

性能工程与可观测性

性能优化的第一原则是测量:不测量就优化是猜。本章讲基准测试的陷阱、采样式剖析器与火焰图、可观测性三支柱与 SLO,以及 Amdahl 定律对优化收益的约束。

  • 不测量就优化是猜:直觉的瓶颈通常错(真正的热点常在想象之外,→ 存储层次与缓存);
  • 先测量,再假设,再验证:profiler 定位热点 → 针对性假设 → 改动后再测。
  • 预热:JIT(→ 运行时、内存管理与垃圾回收)与缓存需要预热,冷启动数字不可信;
  • 平均值的谎言:延迟是长尾分布,平均值掩盖 p99(见下);
  • 编译器删代码:基准里的"死代码"被优化掉(基准框架的陷阱,JMH 一类工具防);
  • 环境噪声:共享 CPU(云主机邻居)、频率调节、NUMA——基准要隔离与多次重复。

采样式剖析器(perf、py-spy、async-profiler):定期采样调用栈,统计热点——开销低(<1%),可上生产。火焰图(flame graph)把采样栈叠成矩形:宽 = 占用时间多——一眼看出热点路径。

剖析的两条纪律:

  • 固定变量:一次只改一个东西,否则归因错误;
  • 全栈采样:含内核态(系统调用、锁等待)——只看用户态会漏掉"时间花在内核"的真相。

p99 比平均值重要:用户体验由尾部决定——1000 个请求里 10 个慢 5 秒,平均值几乎不变,但那 10 个用户不会回来。

  • p50/p95/p99/p999:尾部延迟的分级观测;
  • 尾延迟放大:一次页面请求串行调 10 个后端(每个 p99 = 100ms)→ 页面 p99 ≈ 1s——尾部的乘法效应(并行调用、hedged request 是对策);
  • GC 停顿、缓存过期风暴、锁竞争是 p99 的常客(→ 运行时、内存管理与垃圾回收)。
支柱回答成本
日志发生了什么(细节、上下文)存储贵,采样与分级
指标系统状态如何(数值时序)便宜,可告警
追踪一次请求经过了哪里采样(1–10%),上下文传播
  • 结构化日志(JSON)+ 集中聚合(ELK/Loki);
  • 指标:RED 方法(Rate、Errors、Duration)面向服务、USE 方法(Utilization、Saturation、Errors)面向资源;
  • 分布式追踪:trace ID 沿调用链传播(OpenTelemetry 标准)——微服务的"这次请求慢在哪"只能靠追踪(→ 架构与权衡)。

三支柱的分工:指标发现"有问题"(告警),追踪定位"在哪一段",日志解释"具体为什么"。

  • SLI(指标:可用性、延迟)、SLO(目标:99.9%)、SLA(合同:违约赔偿);
  • 错误预算:1−SLO1 - \text{SLO} 的允许失败量——预算内的失败是允许的(用于发布与实验),预算烧完就冻结发布、专注可靠性——SRE 的核心机制:用预算把"稳定性 vs 迭代速度"变成可度量的决策。
S=1(1−p)+p/n,S = \frac{1}{(1 - p) + p / n},

pp 为可并行比例、nn 为核数——串行部分限制总加速比:95% 可并行的程序,n→∞n \to \infty 也只有 20 倍。含义:

  • 优化前看占比:把 5% 的部分优化到零,上限就是 5% 的总收益——先优化占比大的;
  • 扩展的极限:加核(扩容)的收益被串行部分(锁、协调、单点)封顶——与系统设计的"串行部分"(单点数据库、全局锁)对应(→ 系统设计工具箱)。
  1. 指标告警(哪个服务哪个指标异常);
  2. 追踪定位(这次请求慢在哪一段);
  3. 剖析深挖(那段代码的热点在哪行);
  4. 日志取证(上下文与异常细节);
  5. 改动后验证(指标与 p99 回落)。

每个环节都有工具,工具链先于救火建好——可观测性是平时投入、火灾时救命的投资。

  • 不测量就优化是猜;基准的陷阱:预热、平均值、死代码、环境噪声
  • 采样剖析 + 火焰图:宽 = 时间多;全栈采样含内核态
  • p99 比平均值重要;尾部有乘法效应(串行调用链)
  • 三支柱分工:指标发现、追踪定位、日志解释;错误预算把稳定性变成可度量决策
  • Amdahl 定律:串行部分限制总加速比——先优化占比大的