如何分析性能测试结果,如何进行优化,代码级别监控优化
先看哪些指标
性能分析不能只看平均值。核心指标:
- 吞吐:RPS/TPS、请求数、事务数。
- 延迟:p50、p90、p95、p99、最大延迟。
- 错误率:HTTP 错误、业务错误、超时、重试。
- 并发:VU、连接数、线程数、队列长度。
- 资源:CPU、内存、磁盘 I/O、网络、GC。
- 依赖:数据库、缓存、MQ、第三方接口。
两个常用方法:
- RED:Rate(请求率)、Errors(错误率)、Duration(延迟)。
- USE:Utilization(使用率)、Saturation(饱和度)、Errors(错误)。
分析流程
确认测试有效性
- 压测机是否成为瓶颈?
- 数据量、业务比例、思考时间是否接近真实?
- 是否只压了单接口,没有覆盖链路?
看整体曲线
- 加压阶段:延迟是否随并发线性上升?
- 稳态阶段:是否出现抖动、错误、超时?
- 拐点:哪个并发/ RPS 开始明显变差?
定位瓶颈层
- 客户端:压测机 CPU、网络、端口耗尽。
- 网络/网关:带宽、连接数、限流、超时。
- 应用:CPU、GC、线程池、连接池、锁。
- 数据库:慢查询、锁等待、连接数、磁盘。
- 缓存/MQ:命中率、延迟、堆积。
- 外部依赖:第三方接口超时。
下钻到代码
- 看 trace 的慢 span。
- 看火焰图/CPU profile。
- 看慢查询和执行计划。
- 看日志中的超时、重试、异常。
形成假设并验证
- 每次只改一个点。
- 改完重新压测,对比同一指标。
- 保留基线和回归测试。
常见瓶颈
应用层
- N+1 查询:循环里查数据库。
- 同步阻塞:在请求线程里做大量计算、文件 IO、远程调用。
- 锁竞争:全局锁、长事务、热点 key。
- 序列化开销:大对象 JSON、重复序列化。
- 连接池过小:数据库/HTTP 连接等待。
- 线程池配置不合理:队列堆积或频繁创建线程。
- 内存泄漏:缓存无上限、对象未释放。
- GC 频繁:堆太小、对象分配过多。
数据库
- 缺索引、索引失效。
- 隐式类型转换。
like '%xxx%'全表扫描。- 大偏移分页。
- 长事务和锁等待。
- 连接数打满。
- 批量写入逐条提交。
缓存和 MQ
- 缓存命中率低。
- 缓存击穿/雪崩。
- 大 key、热 key。
- MQ 堆积、消费慢。
- 重试导致重复消费。
基础设施
- 带宽不足。
- 连接数限制。
- 磁盘 IOPS 不足。
- 容器 CPU/内存 limit 过低。
- 负载均衡策略不合理。
代码级优化
1. 减少数据库访问
# 不好:N+1
for order in orders:
user = db.query_user(order.user_id)
# 好:批量查询
users = db.query_users([o.user_id for o in orders])
user_map = {u.id: u for u in users}
2. 批量写入
# 不好:逐条提交
for item in items:
db.insert(item)
# 好:批量插入
db.bulk_insert(items)
3. 避免阻塞
- 把耗时操作放到异步任务/队列。
- 用非阻塞 IO 或线程池。
- 设置合理的超时和熔断。
- 不要在请求链路里做大文件处理。
4. 缓存
- 热点数据放 Redis 或本地缓存。
- 设置 TTL 和容量上限。
- 防击穿:互斥锁/逻辑过期。
- 防雪崩:随机 TTL、降级。
5. 减少序列化
- 只返回必要字段。
- 避免重复序列化/反序列化。
- 大响应考虑分页或流式返回。
6. 锁和事务
- 缩小锁范围。
- 避免长事务。
- 用乐观锁/版本号替代大范围悲观锁。
- 热点账户/库存考虑分段或队列。
7. JVM/运行时
- 调整堆大小和 GC 参数。
- 分析 GC 日志,减少 Full GC。
- Node.js 避免阻塞事件循环,CPU 密集任务用 worker。
- Python 用多进程/异步,避免 GIL 影响。
代码级监控
指标
- 接口耗时、错误率、QPS。
- 数据库查询次数、慢查询数、连接池使用率。
- 缓存命中率、Redis 延迟。
- 线程池活跃数、队列长度。
- GC 次数和耗时。
- 自定义业务指标:下单成功率、支付成功率。
工具
- APM:OpenTelemetry、SkyWalking、Pinpoint、Datadog、New Relic。
- 指标:Prometheus + Grafana。
- 日志:ELK/EFK、Loki。
- 链路追踪:Jaeger、Tempo、SkyWalking。
- Profiling:async-profiler(Java)、py-spy(Python)、clinic/--prof(Node)、pprof(Go)、Pyroscope(持续 profiling)。
埋点示例
import time
from prometheus_client import Histogram, Counter
REQUEST_DURATION = Histogram(
"api_request_duration_seconds",
"API request duration",
["path", "method", "status"],
)
DB_QUERY_COUNT = Counter(
"db_query_total",
"Database query count",
["operation", "table"],
)
def timed(path, method):
def decorator(fn):
def wrapper(*args, **kwargs):
start = time.perf_counter()
status = "200"
try:
return fn(*args, **kwargs)
except Exception:
status = "500"
raise
finally:
REQUEST_DURATION.labels(path, method, status).observe(
time.perf_counter() - start
)
return wrapper
return decorator
优化闭环
- 建立基线:版本、配置、数据量、指标。
- 压测并定位瓶颈。
- 只改一个点,重新压测。
- 对比 p95/p99、错误率、资源使用。
- 把关键指标做成看板和告警。
- 把核心场景加入 CI 性能回归,防止回退。
面试回答模板
我会先确认压测有效性,避免压测机或数据失真;然后看 RPS、p95/p99、错误率和资源指标,用 RED/USE 方法定位是客户端、应用、数据库、缓存还是外部依赖的问题。应用层重点看 N+1、阻塞调用、锁竞争、连接池和 GC;数据库看慢查询、索引、锁等待和连接数。优化时每次只改一个点,改完重新压测对比。代码级会用 APM、Prometheus、trace 和 profiling 做持续监控,把核心指标接入看板和 CI 性能回归。