概述
性能问题几乎是每个 SRE 都会遇到的高频场景:用户反馈"好慢"、告警说"P99 延迟超标"、监控显示"CPU 快满了"。但很多团队对性能问题的处理方式是"哪里高了调哪里"——CPU 高了就加机器,SQL 慢了就加索引,延迟高了就加缓存。这种头痛医头的做法短期内可能有效,但长期来看会让系统越来越复杂、成本越来越高、问题越来越难排查。
性能工程(Performance Engineering)与性能调优(Performance Tuning)有本质区别。性能调优是"发现问题→优化"的反应式过程;性能工程是"建立基线→持续度量→主动发现→系统优化"的工程化体系。SRE 的视角不是"让某个接口快 10ms",而是"建立系统性的性能管理体系,让性能问题在被用户感知之前发现和解决"。
从方法论、分析框架、基线建立、瓶颈定位、优化策略到持续管理,详细梳理 SRE 视角的性能工程。
关于性能分析的系统性方法,可参考 Brendan Gregg - USE Method 和 Tom Wilkie - RED Method。
一、性能工程 vs 性能调优
概念区分
| 维度 | 性能调优 | 性能工程 |
|---|---|---|
| 时机 | 性能问题出现后 | 贯穿系统全生命周期 |
| 目标 | 解决当前的性能问题 | 建立持续的性能管理体系 |
| 方法 | 经验驱动,试试看 | 数据驱动,详细分析 |
| 范围 | 聚焦特定瓶颈 | 覆盖全栈(应用→中间件→基础设施) |
| 产出 | 问题解决 | 基线、SLO、监控、优化策略 |
| 持续性 | 一次性 | 持续度量和管理 |
为什么 SRE 需要性能工程
没有性能工程的团队:
用户投诉"慢" → 紧急排查 → 发现 SQL 慢 → 加索引
→ 一个月后又慢了 → 发现是缓存命中率低 → 加缓存
→ 又一个月后又慢了 → 发现是连接池不够 → 调连接池
→ 循环往复,系统越来越复杂,问题越来越多
有性能工程的团队:
建立性能基线 → 持续监控 → 发现 P99 缓慢上升(用户还没感知)
→ 主动分析 → 定位到数据库查询模式变化 → 优化查询
→ 在用户感知之前解决问题
性能工程的价值:
- 提前发现:在用户感知之前发现性能劣化
- 系统优化:不是头痛医头,而是系统性提升
- 成本控制:合理的资源使用,而非盲目扩容
- 容量规划:基于性能数据做容量预测
- 架构改进:驱动架构层面的性能优化
二、性能分析的黄金信号
Google SRE 的四大黄金信号
Google SRE Book 定义了监控分布式系统的四个黄金信号:
| 信号 | 含义 | 关注点 |
|---|---|---|
| 延迟(Latency) | 请求处理时间 | 区分成功和失败的请求延迟 |
| 流量(Traffic) | 请求量 | QPS、TPS、并发数 |
| 错误(Errors) | 错误率 | 5xx、业务错误、超时 |
| 饱和度(Saturation) | 资源使用率 | CPU、内存、IO、网络、连接数 |
四大黄金信号的关系:
流量 ↑ → 饱和度 ↑ → 延迟 ↑ → 错误 ↑
性能问题的典型演化路径:
1. 流量增长
2. 资源饱和度上升
3. 延迟开始劣化
4. 超过阈值后开始报错
理想的监控在 Step 2-3 就能发现问题,而不是等到 Step 4。
延迟的正确度量
# 延迟度量的常见陷阱
latency_measurement:
wrong:
- "只看平均延迟——平均值掩盖了长尾问题"
- "成功和失败请求混在一起——失败的请求可能拉高平均延迟"
- "只看 P50——50% 的用户体验可能不代表整体"
correct:
- "使用百分位:P50/P95/P99/P99.9"
- "区分成功和失败的请求延迟"
- "关注长尾:P99 比 P50 更能反映用户体验"
- "使用直方图而非平均值——能看到分布形态"
# Prometheus 中的正确延迟度量
# ❌ 错误:平均值
avg(http_request_duration_seconds)
# ✅ 正确:百分位
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
# ✅ 正确:区分成功和失败
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{status!~"5.."}[5m])) by (le)
)
延迟分布的秘密
典型的延迟分布:
请求数
│
│ ██
│ ████
│██████
│████████
│██████████
│█████████████
│████████████████████████████████ ████ ██
└──────────────────────────────────────────────→ 延迟
0ms 10ms 20ms 50ms 100ms 200ms 500ms 1s 5s
大部分请求在 10-50ms,但有长尾在 500ms-5s。
P50 = 30ms ← 看起来很好
P95 = 100ms ← 还行
P99 = 800ms ← 1% 的用户体验很差
P99.9 = 3s ← 0.1% 的用户快超时了
如果只看 P50,你不会知道有 1% 的用户等了 800ms。
三、USE 方法
方法概述
USE 方法由 Netflix 的 Brendan Gregg 提出,用于分析资源的性能:
USE = Utilization(使用率)× Saturation(饱和度)× Errors(错误)
对每种资源,检查这三个维度:
| 维度 | 定义 | 度量方式 |
|---|---|---|
| Utilization | 资源被使用了多少时间 | CPU 使用率、带宽利用率 |
| Saturation | 资源排队/等待的程度 | 运行队列长度、等待 IO 的请求数 |
| Errors | 资源的错误计数 | 网络丢包、磁盘错误、OOM |
资源清单与检查矩阵
服务器资源清单:
├── CPU
├── 内存
├── 网络接口
├── 存储设备(磁盘/SSD)
├── 存储控制器
├── 网络控制器
├── 互连(PCIe、NUMA)
└── 内核资源(文件描述符、连接跟踪表)
| 资源 | Utilization | Saturation | Errors |
|---|---|---|---|
| CPU | CPU 使用率 % | 运行队列长度 | - |
| 内存 | 已用内存 % | swap 使用、页面扫描 | OOM kill 次数 |
| 网络 | 带宽利用率 % | 网卡队列深度 | 丢包率、重传率 |
| 磁盘 | 忙时间 % | IO 等待队列长度 | IO 错误、坏块 |
| 存储 | 容量使用率 % | - | 文件系统错误 |
USE 方法的实践
# CPU 检查
# Utilization
top -bn1 | grep "Cpu(s)"
# Saturation(运行队列长度)
uptime # load average > CPU 核数说明饱和
# 内存检查
# Utilization
free -m
# Saturation(页面扫描)
vmstat 1 | grep -E "si|so" # si/so > 0 说明在 swap
# Errors
dmesg | grep -i "oom"
# 网络检查
# Utilization
sar -n DEV 1 # 带宽使用
# Saturation
ifconfig # 看 drops/overruns
# Errors
ip -s link # RX/TX errors
# 磁盘检查
# Utilization
iostat -xz 1 # %util
# Saturation
iostat -xz 1 # avgqu-sz(平均队列长度)
# Errors
dmesg | grep -i "error\|fail" | grep -i "disk\|scsi"
USE 方法的应用原则
USE 方法使用流程:
1. 列出所有资源
→ CPU、内存、网络、磁盘...
2. 对每种资源检查 U/S/E
→ 有没有使用率过高?
→ 有没有饱和?
→ 有没有错误?
3. 发现瓶颈
→ 第一个饱和的资源通常就是瓶颈
4. 深入分析
→ 为什么这个资源饱和?
→ 是哪个进程/请求在消耗?
5. 优化
→ 优化消耗最多的来源
→ 或扩容该资源
四、RED 方法
方法概述
RED 方法由 Tom Wilkie(Prometheus 作者之一)提出,用于分析服务的性能:
RED = Rate(请求速率)× Errors(错误)× Duration(延迟)
| 维度 | 定义 | 度量方式 |
|---|---|---|
| Rate | 请求量(QPS) | 每秒请求数 |
| Errors | 错误率 | 失败请求占比 |
| Duration | 响应时间 | P50/P95/P99 延迟 |
USE vs RED 的分工
USE 和 RED 的互补关系:
RED 方法(服务视角) USE 方法(资源视角)
┌───────────────┐ ┌───────────────┐
│ Rate │ │ CPU │
│ Errors │ ────→ 为什么要看资源?──→ │ Memory │
│ Duration │ │ Network │
└───────────────┘ │ Disk │
"服务表现如何?" └───────────────┘
"资源够不够用?"
分析流程:
1. 先用 RED 发现服务问题(延迟高?错误多?)
2. 再用 USE 定位资源瓶颈(哪个资源饱和了?)
3. 结合两者找到根因
RED 方法的 Prometheus 实现
# Rate: 每秒请求数
sum(rate(http_requests_total[5m])) by (service)
# Errors: 错误率
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
# Duration: P99 延迟
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le)
)
RED 仪表盘设计
RED 仪表盘布局(每个服务一行):
服务名 | Rate (QPS) | Error Rate | P50 Latency | P99 Latency
----------|------------|------------|-------------|------------
payment | 2,500 | 0.05% | 45ms | 180ms
order | 5,000 | 0.02% | 30ms | 120ms
search | 8,000 | 0.10% | 80ms | 350ms ← P99 偏高
user | 3,000 | 0.01% | 25ms | 90ms
→ 一眼看出哪个服务有性能问题
五、性能基线建立
为什么需要性能基线
性能基线是性能工程的基础。没有基线,你无法回答"现在的性能是正常还是异常":
没有基线:
问:"P99 延迟 500ms 正常吗?"
答:"不知道,没有历史数据对比。"
有基线:
问:"P99 延迟 500ms 正常吗?"
答:"基线 P99 是 200ms,当前 500ms 是基线的 2.5 倍,属于异常劣化。"
性能基线的内容
performance_baseline:
service: "payment-service"
baseline_period: "2026-06-01 ~ 2026-06-30"
# 1. 流量基线
traffic:
avg_qps: 2500
peak_qps: 5000
daily_pattern: "9:00-22:00 为高峰,QPS 3000-5000"
# 2. 延迟基线
latency:
p50: 45ms
p95: 120ms
p99: 200ms
p999: 500ms
# 3. 资源使用基线
resource_usage:
cpu_avg: 35%
cpu_peak: 65%
memory_avg: 4.2GB
memory_peak: 5.1GB
disk_io_avg: "500 IOPS"
network_avg: "50Mbps"
# 4. 错误率基线
error_rate:
avg: 0.03%
peak: 0.1%
# 5. 依赖性能基线
dependencies:
db_query_p99: "50ms"
redis_p99: "5ms"
downstream_api_p99: "100ms"
# 6. 容量基线
capacity:
max_sustainable_qps: 8000 # 在 SLO 内的最大 QPS
cpu_per_1000_qps: 12% # 每 1000 QPS 消耗的 CPU
memory_per_1000_qps: 0.8GB
基线建立方法
# 性能基线计算
def calculate_baseline(metrics_data, period_days=30):
"""
从历史监控数据计算性能基线
"""
baseline = {}
# 1. 计算百分位延迟
latencies = metrics_data['latency']
baseline['latency'] = {
'p50': percentile(latencies, 50),
'p95': percentile(latencies, 95),
'p99': percentile(latencies, 99),
'p999': percentile(latencies, 99.9)
}
# 2. 计算流量模式
qps_data = metrics_data['qps']
baseline['traffic'] = {
'avg_qps': mean(qps_data),
'peak_qps': max(qps_data),
'p95_qps': percentile(qps_data, 95),
}
# 3. 计算资源使用
cpu_data = metrics_data['cpu_usage']
baseline['resource_usage'] = {
'cpu_avg': mean(cpu_data),
'cpu_p95': percentile(cpu_data, 95),
'cpu_peak': max(cpu_data),
}
# 4. 计算异常阈值
# 使用 3-sigma 规则:超过 mean + 3*std 视为异常
baseline['anomaly_thresholds'] = {
'latency_p99': {
'normal': baseline['latency']['p99'],
'warning': baseline['latency']['p99'] * 1.5,
'critical': baseline['latency']['p99'] * 2.0
},
'error_rate': {
'normal': mean(metrics_data['error_rate']),
'warning': mean(metrics_data['error_rate']) * 3,
'critical': mean(metrics_data['error_rate']) * 10
}
}
return baseline
基线更新策略
baseline_update:
frequency: "每月更新一次"
trigger:
scheduled: "每月 1 号,基于上月数据更新基线"
event_driven: "架构变更后重新建立基线"
versioning:
- "保留历史基线版本,用于对比"
- "基线变更超过 20% 时需要分析原因"
alerting_based_on_baseline:
- alert: "LatencyAnomalyDetected"
condition: "current_p99 > baseline_p99 * 1.5"
message: "P99 延迟超过基线的 1.5 倍"
- alert: "ResourceSaturationAnomaly"
condition: "current_cpu > baseline_cpu_p95 * 1.3"
message: "CPU 使用率超过基线 P95 的 1.3 倍"
六、瓶颈定位流程
系统性瓶颈定位方法
当发现性能问题时,按以下流程系统性定位瓶颈:
Step 1: 确认问题
→ 什么指标异常?延迟?错误率?资源使用率?
→ 是突然劣化还是渐进劣化?
→ 影响范围:所有用户?部分用户?特定接口?
Step 2: 黄金信号检查
→ 流量:是否异常增长?
→ 错误:错误类型是什么?
→ 延迟:是 P50 还是 P99 劣化?
→ 饱和度:哪个资源接近饱和?
Step 3: USE 方法定位资源瓶颈
→ CPU 饱和?(负载 > 核心数)
→ 内存饱和?(swap/OOM)
→ 磁盘饱和?(IO wait 高)
→ 网络饱和?(带宽/丢包)
Step 4: RED 方法定位服务瓶颈
→ 哪个服务的延迟/错误异常?
→ 是该服务自身的问题还是依赖的问题?
Step 5: 链路追踪定位具体节点
→ 用 Jaeger/Zipkin 追踪请求链路
→ 找到耗时最长的节点
Step 6: 深入分析
→ CPU profile(哪个函数消耗 CPU?)
→ 火焰图(代码级定位)
→ 慢查询日志(数据库层面)
→ GC 日志(内存/GC 问题)
Step 7: 验证假设
→ 基于分析结果提出假设
→ 用数据验证假设
→ 修复并验证效果
瓶颈定位工具箱
| 层面 | 工具 | 用途 |
|---|---|---|
| 系统层 | top/htop | CPU/内存概览 |
| 系统层 | vmstat | 虚拟内存、CPU 调度 |
| 系统层 | iostat | 磁盘 IO |
| 系统层 | sar | 历史性能数据 |
| 系统层 | perf | CPU 性能分析 |
| 网络层 | tcpdump | 网络包分析 |
| 网络层 | ss/netstat | 连接状态 |
| 应用层 | pprof(Go) | Go 应用 profile |
| 应用层 | JProfiler(Java) | Java 应用 profile |
| 数据库 | EXPLAIN | SQL 执行计划 |
| 数据库 | slow query log | 慢查询 |
| 链路追踪 | Jaeger/Zipkin | 分布式链路追踪 |
| 可视化 | 火焰图 | 函数调用耗时可视化 |
火焰图分析
火焰图是性能分析的利器,能直观展示 CPU 时间花在哪里:
# Go 应用的火焰图生成
# 1. 启用 pprof
import _ "net/http/pprof"
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
# 2. 采集 CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# 3. 生成火焰图
(pprof) web # 在浏览器中打开火焰图
火焰图示例(简化):
┌─────────────────────────────────────────────────────┐
│ main.main │
├──────────────┬──────────────────────────────────────┤
│ http.Handle │ db.Query │
│ ├──────────┬───────────────────────────┤
│ │ db.Exec │ pgConn.Query │
│ │ ├──────────┬────────────────┤
│ │ │ network │ parseResult │
│ │ │ 200ms │ 50ms │
└──────────────┴──────────┴──────────┴────────────────┘
→ 一眼看出 db.Query 占了大部分时间
→ 其中 network(数据库网络往返)占 200ms
→ 优化方向:减少数据库查询次数或使用连接池
数据库性能分析
-- 1. 查看慢查询
SELECT * FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- 2. 分析执行计划
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 12345;
-- 3. 检查索引使用情况
SELECT
schemaname,
relname,
seq_scan, -- 全表扫描次数
seq_tup_read, -- 全表扫描读取的行数
idx_scan, -- 索引扫描次数
idx_tup_fetch -- 索引扫描获取的行数
FROM pg_stat_user_tables
ORDER BY seq_scan DESC;
-- 4. 检查锁等待
SELECT
pid,
state,
wait_event_type,
wait_event,
query
FROM pg_stat_activity
WHERE wait_event IS NOT NULL;
七、优化优先级矩阵
优化的 ROI 分析
不是所有性能优化都值得做。需要评估投入产出比:
优化 ROI = (性能提升幅度 × 影响范围 × 持续时间) / (开发成本 + 风险成本)
示例:
优化A: 加索引
性能提升:查询从 500ms → 50ms(10倍)
影响范围:所有用户
持续时间:永久
开发成本:0.5 天
风险成本:低
→ ROI 极高,优先做
优化B: 重构数据访问层
性能提升:P99 从 200ms → 150ms(25%)
影响范围:所有用户
持续时间:永久
开发成本:2 周
风险成本:中(重构可能引入 bug)
→ ROI 中等,排期做
优化C: 换编程语言
性能提升:预期 30% 但不确定
影响范围:全系统
开发成本:6 个月
风险成本:高
→ ROI 极低,不建议
优化优先级矩阵
| 高影响 | 低影响 | |
|---|---|---|
| 低成本 | 🔴 优先做 加索引、加缓存、调参数 | 🟡 有空做 代码微优化 |
| 高成本 | 🟡 排期做 架构优化、数据库分片 | 🟢 不做 过度优化 |
常见优化策略及优先级
optimization_strategies:
# 优先级 1:低改动高收益
database:
- strategy: "添加/优化索引"
effort: "低"
impact: "高"
example: "给 WHERE 条件字段加索引"
- strategy: "优化慢查询"
effort: "低"
impact: "高"
example: "避免 SELECT *,用覆盖索引"
- strategy: "连接池优化"
effort: "低"
impact: "中"
example: "调整连接池大小和超时"
# 优先级 2:中改动中收益
application:
- strategy: "缓存热点数据"
effort: "中"
impact: "高"
example: "Redis 缓存商品信息"
- strategy: "批量操作替代循环"
effort: "低"
impact: "中"
example: "批量 INSERT 替代循环 INSERT"
- strategy: "异步化"
effort: "中"
impact: "中"
example: "非核心逻辑用消息队列异步处理"
# 优先级 3:高改动高收益
architecture:
- strategy: "读写分离"
effort: "高"
impact: "高"
example: "读请求走从库"
- strategy: "数据库分片"
effort: "高"
impact: "高"
example: "按用户 ID 分片"
- strategy: "CDN 加速"
effort: "中"
impact: "高"
example: "静态资源走 CDN"
# 不建议
anti_patterns:
- "过早优化:没有基线数据就盲目优化"
- "微优化:花一周时间把 1ms 优化到 0.9ms"
- "过度设计:QPS 100 就考虑分库分表"
- "只优化应用层:忽视数据库和基础设施"
优化效果验证
# 性能优化的验证流程
optimization_validation:
before:
- "记录优化前的性能基线(P50/P95/P99/错误率)"
- "确保监控数据完整"
during:
- "灰度发布优化代码"
- "对比优化前后的指标"
after:
- "验证性能提升幅度"
- "确认无功能回归"
- "观察至少 24 小时的稳定性"
- "更新性能基线"
# A/B 测试验证
ab_test:
control_group: "未优化版本"
experiment_group: "优化版本"
metrics: ["P50", "P95", "P99", "error_rate", "cpu_usage"]
duration: "至少 1 小时高流量时段"
八、持续性能管理
性能回归检测
性能不是一次性的工作——代码变更可能引入性能回归。需要建立持续的性能回归检测机制:
# 性能回归检测
performance_regression_detection:
ci_cd_integration:
# 每次发布前做性能基准测试
pre_deploy:
- name: "运行性能基准测试"
script: |
# 对比当前版本与基线的性能
./benchmark.sh --compare baseline
# 如果 P99 劣化超过 10%,阻止发布
if [ $(echo "$P99_REGRESSION > 0.1" | bc) -eq 1 ]; then
echo "Performance regression detected: P99 degraded by $P99_REGRESSION"
exit 1
fi
continuous_monitoring:
# 持续监控性能指标
- alert: "PerformanceRegressionDetected"
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[1h])) by (le)
) > 250
for: 10m
labels:
severity: warning
annotations:
summary: "P99 延迟超过基线阈值(250ms)"
description: "当前 P99: {{ $value }}ms,基线 P99: 200ms"
性能预算
性能预算是把性能要求变成可管理的约束:
# 性能预算示例
performance_budget:
service: "payment-service"
latency_budget:
total_p99: "500ms" # 端到端 P99 预算
breakdown:
api_gateway: "20ms" # 网关
app_logic: "100ms" # 应用逻辑
db_query: "200ms" # 数据库
cache: "10ms" # 缓存
network: "20ms" # 网络
downstream: "150ms" # 下游依赖
# 注意:各部分预算之和不等于总预算
# 因为不是所有部分都同时出现
resource_budget:
cpu_per_request: "10ms CPU time"
memory_per_request: "5MB"
db_connections: "max 20 concurrent"
enforcement:
# 在 CI 中检查性能预算
- "单元测试中 mock 依赖,测量纯应用逻辑耗时"
- "集成测试中测量端到端延迟"
- "如果超出预算,CI 失败"
性能 Review
# 月度性能 Review 模板
## 概览
- 服务名称:payment-service
- 评审周期:2026 年 7 月
- 负责人:张三
## 性能趋势
| 指标 | 上月 | 本月 | 变化 | 状态 |
|------|------|------|------|------|
| P50 延迟 | 45ms | 42ms | -7% | ✅ 改善 |
| P99 延迟 | 200ms | 230ms | +15% | ⚠️ 劣化 |
| 错误率 | 0.03% | 0.04% | +33% | ⚠️ 劣化 |
| CPU 均值 | 35% | 42% | +20% | ⚠️ 上升 |
| QPS 均值 | 2500 | 3100 | +24% | 流量增长 |
## 分析
1. P99 延迟劣化 15%,主要原因是 QPS 增长 24% 导致 CPU 使用率上升
2. 错误率轻微上升,与延迟劣化相关(超时增加)
3. 当前 CPU 42%,尚在安全范围,但需要关注增长趋势
## 行动项
- [ ] 分析 P99 劣化的具体接口,是否可以优化
- [ ] 评估是否需要扩容(当前 CPU 接近 50% 预警线)
- [ ] 下周做一次数据库慢查询分析
性能文化与团队协作
performance_culture:
principles:
- "性能是所有人的责任,不只是 SRE 的"
- "性能问题用数据说话,不凭感觉"
- "建立基线,持续度量,主动发现"
- "优化要有 ROI 分析,不做无意义优化"
developer_practices:
- "代码 review 时关注性能影响"
- "新增 API 时设定延迟预算"
- "数据库变更时检查执行计划"
- "写单元测试时包含性能断言"
sre_practices:
- "维护性能基线和监控"
- "定期做性能审计"
- "驱动跨团队的性能优化"
- "建立性能预算和回归检测"
九、性能优化的常见误区
误区一:“加机器解决一切”
误区:性能不好 → 加 CPU/加内存/加节点
问题:
- 如果是 SQL 慢查询,加机器只是延缓问题
- 如果是锁竞争,加机器可能更糟(分布式锁更慢)
- 成本持续增长,不可持续
正确做法:
→ 先定位瓶颈
→ 如果是资源不足 → 扩容
→ 如果是代码/SQL 问题 → 优化代码
→ 如果是架构问题 → 改架构
误区二:“缓存解决一切”
误区:慢 → 加缓存
问题:
- 缓存引入一致性复杂性
- 缓存命中率低时无效
- 缓存本身可能成为瓶颈
正确做法:
→ 先优化数据源(数据库/索引)
→ 再考虑缓存
→ 缓存是优化手段,不是掩盖问题的工具
误区三:“只看平均值”
误区:平均延迟 50ms,没问题!
问题:
- 平均值掩盖了长尾
- 1% 的用户可能等了 5 秒
正确做法:
→ 看 P95/P99
→ 看延迟分布
→ 关注长尾
误区四:“过早优化”
误区:系统还没上线,就开始纠结 1ms 的优化
问题:
- 浪费时间在非瓶颈上
- 增加代码复杂度
- 优化可能基于错误的假设
正确做法:
→ 先让它工作
→ 再让它正确
→ 最后让它快速(基于数据优化)
总结
性能工程是 SRE 体系中最考验工程深度的领域之一。核心要点:
- 性能工程 ≠ 性能调优:性能工程是贯穿系统全生命周期的持续管理体系,不是问题驱动的临时优化
- 黄金信号是起点:延迟、流量、错误、饱和度——四个信号覆盖了性能监控的核心维度
- USE 方法看资源:对每种资源检查使用率、饱和度、错误,系统性发现资源瓶颈
- RED 方法看服务:请求速率、错误率、延迟——从服务视角发现性能问题
- 基线是基础:没有基线就没有"正常"和"异常"的判断标准
- 瓶颈定位有方法:黄金信号 → USE → RED → 链路追踪 → Profile,逐层深入
- 优化要算 ROI:低改动高收益优先做,高改动低收益不做
- 持续管理是关键:性能回归检测、性能预算、定期 Review——让性能持续可控
记住性能工程的核心原则:Measure, don’t guess(度量,不要猜测)。 所有性能优化决策都应该基于数据——先建立基线,再发现问题,然后定位瓶颈,最后优化验证。跳过度量直接优化,你很可能在优化一个不是瓶颈的地方。
最后引用 Donald Knuth 的名言:“过早优化是万恶之源。” 但要补充一句:“该优化时不优化,也是万恶之源。” 关键在于:基于数据判断什么时候该优化、优化什么、怎么优化——这正是性能工程的工程价值所在。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Brendan Gregg - USE Method — Brendangregg,参考了Brendan Gregg - USE Method相关内容
- Tom Wilkie - RED Method — Grafana,参考了Tom Wilkie - RED Method相关内容