Skip to content

第 19 章 · 监控与结果分析

本章目标:理解 p50/p95/p99 百分位指标的含义,掌握 Locust 与 Prometheus/Grafana 的集成方法,学会系统性地定位性能瓶颈。

19.1 理解响应时间百分位

平均响应时间是最容易误导人的指标——它会被大量快请求稀释掉少量慢请求的影响。生产级性能分析必须关注尾部延迟:

text
100 个请求的响应时间分布:

  p50 (中位数) = 120ms   ← 一半用户比这快
  p90          = 350ms   ← 90% 用户比这快
  p95          = 500ms   ← 95% 用户比这快(SLA 常用标准)
  p99          = 1200ms  ← 尾部 1% 用户可能等 1.2 秒!
  平均值       = 180ms   ← 被大量快请求拉低,掩盖了问题

为什么平均值会骗人

假设 95 个请求耗时 50ms,5 个请求耗时 2000ms:

  • 平均值 = (95×50 + 5×2000) / 100 = 147ms ← 看起来还行
  • p95 = 2000ms ← 暴露了真实问题!

如果你的 SLA 是"99% 的请求在 500ms 内完成",平均值完全无法反映真实情况。

Locust 内置统计解读

Locust Web UI 和 CSV 报告中的关键列:

列名含义关注点
Average平均响应时间参考值
Median (50%)中位数正常用户体验
95%p95SLA 阈值常用
98%p98高要求场景
99%p99极端尾部延迟
RPS每秒请求数吞吐量
Failures/s失败率稳定性

19.2 Prometheus + Grafana 集成

Locust 支持通过事件钩子将统计数据推送到 Prometheus,实现实时监控和历史趋势分析。

方式一:使用 locust-exporter(推荐)

locust-exporter 是一个独立的 Prometheus exporter,直接从 Locust master 的 API 拉取数据:

yaml
# docker-compose.yml — 一键启动完整监控栈
version: "3.8"
services:
  locust:
    image: locustio/locust
    ports:
      - "8089:8089"
    volumes:
      - ./locustfile.py:/mnt/locust/locustfile.py
    command: -f /mnt/locust/locustfile.py --master

  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
yaml
# prometheus.yml
scrape_configs:
  - job_name: "locust"
    static_configs:
      - targets: ["locust:8089"]
    metrics_path: /export/prometheus

方式二:自定义事件钩子推送

python
import time
from locust import events


class MetricsCollector:
    """收集自定义指标并通过 OpenTelemetry 上报"""

    def __init__(self):
        self.request_count = 0
        self.error_count = 0
        self.total_response_time = 0.0
        self.slow_requests = []


@events.request.add_listener
def track_metrics(request_type, name, response_time, response_length, exception, **kwargs):
    collector.request_count += 1
    collector.total_response_time += response_time

    if exception:
        collector.error_count += 1

    if response_time > 800:
        collector.slow_requests.append({
            "name": name,
            "time": response_time,
            "timestamp": time.time()
        })

19.3 实时图表解读

Locust 自带的 Web UI 提供两张核心图表:

Total Requests per Second(RPS 图)

  • 绿色线:成功的请求/秒
  • 红色线:失败的请求/秒
  • 黄色区域:当前并发用户数

Response Times(响应时间图)

  • 展示中位数和 95% 百分位随时间的变化

图表异常模式识别

模式可能原因
RPS 锯齿状剧烈波动wait_time 太短或被测服务不稳定
RPS 平坦但用户数增加被测系统已饱和(吞吐量上限)
响应时间阶梯式上升连接池耗尽或数据库锁竞争
失败率突然飙升服务重启、连接超时、资源耗尽
响应时间周期性尖刺GC 暂停或定时任务冲突

19.4 瓶颈定位方法论

当发现性能问题时,按以下顺序排查:

text
第 1 步:确认压测工具本身不是瓶颈
         └→ 检查 Locust CPU < 80%(见第 17 章)

第 2 步:区分"所有接口都慢"还是"特定接口慢"
         └→ 看 Locust 统计表中各接口的分列数据

第 3 步:如果只有特定接口慢
         ├→ 该接口是否涉及数据库查询?
         │   └→ 用 EXPLAIN ANALYZE 检查慢 SQL
         ├→ 是否调用了外部 API?
         │   └→ 检查外部服务的响应时间
         └→ 是否有 N+1 查询问题?

第 4 步:如果所有接口都慢
         ├→ 检查服务器 CPU/内存/磁盘 I/O
         ├→ 检查数据库连接池大小
         └→ 检查线程池/workers 配置

实战案例:定位数据库连接池瓶颈

python
# 场景:增加用户数后 RPS 不再增长,p95 从 200ms 升到 2000ms

# 1. 在被测 FastAPI 应用添加诊断日志
import time
from contextlib import asynccontextmanager


@asynccontextmanager
async def timed_db_session():
    start = time.monotonic()
    session = SessionLocal()
    acquire_time = time.monotonic() - start
    if acquire_time > 0.1:  # 获取连接超过 100ms 说明池不够
        logger.warning(f"DB pool wait: {acquire_time:.3f}s")
    try:
        yield session
    finally:
        session.close()

# 2. 日志显示 "DB pool wait: 1.2s" → 确认是连接池太小
# 3. 解决方案:增大 pool_size 或使用 NullPool

本章小结

  • 平均响应时间会掩盖尾部延迟,必须看 p95/p99;
  • Prometheus + Grafana 提供 Locust 数据的持久化和可视化;
  • RPS 图表的异常形态能快速指向问题类型;
  • 排查顺序:先排除 Locust 自身 → 区分全局/局部 → 逐层深入。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 为什么 p95 比平均响应时间更有参考价值?

2. RPS 曲线平坦但用户数持续增加,最可能的原因是什么?

3. 发现某个特定 API 的 p99 远高于其他接口,应该首先检查什么?

4. 以下哪种现象最可能表明存在数据库连接池不足的问题?

🛠️ 动手实践

  1. 对 httpbin.org 做 3 分钟压测,导出 CSV 后用 Python pandas 绘制 p50/p95/p99 三条曲线。
  2. 搭建一个包含 Locust + Prometheus + Grafana 的 Docker Compose 环境,观察 Grafana 中的实时 RPS 图。
  3. 故意在一个 FastAPI 接口中加入 time.sleep(2),然后通过 Locust 统计数据找出这个"慢接口",记录你的排查过程。

完成练习后,进入下一章:综合实战