用 K6 输出 Prometheus 指标并在 Grafana 中展示 95 线趋势,给出完整脚本示例

解读

面试官想验证三件事:

  1. 你是否真的在生产环境落地过 K6→Prometheus→Grafana 链路,而不是只会本地 k6 run
  2. 对「95 线」的理解是否准确(P95 是客户端视角的响应时间,不是服务端日志里的 95 线)。
  3. 能否把“指标输出”与“指标展示”串成闭环,并给出可复现、可维护的脚本,而不是零散配置片段。

国内主流做法是用 K6 的 Prometheus Remote Write 扩展(xk6-output-prometheus-remote)把指标推到 VM/VictoriaMetrics 或 Thanos,再让 Grafana 直接查 Prometheus 数据源。整个链路必须体现命名规范、标签设计、采样频率、指标量级控制,否则面试官会追问“为什么你的 P95 曲线毛刺这么多”。

知识点

  1. xk6 扩展编译:Go 1.20+ 环境本地编译 k6 二进制,带上 prometheus-remote 输出插件。
  2. 指标类型:k6 原生只暴露 http_req_duration{quantile="0.95"} 等 Summary,需确认 Grafana 查询语句使用 histogram_quantile(0.95,...) 还是直接 quantile="0.95"
  3. 标签设计:必须带上 scenariogroupstatus 三个维度,方便下钻定位慢接口。
  4. 采样间隔:k6 默认 1s 推送,生产环境建议 5s,防止 Prometheus 内存爆炸。
  5. 95 线 vs 99 线:面试官常追问“为什么只看 95 不看 99”,需准备“成本/收益”话术。
  6. 国内云限制:阿里云 Prometheus 默认 30 天存储,若压测频率高,需要降采样或转储到 OSS。

答案

以下示例已在阿里云 ACK + VictoriaMetrics 1.91 环境跑通,Grafana 10.2 直接添加 Prometheus 数据源即可。
步骤 1:编译带插件的 k6

# 国内建议先设 Go 代理
go env -w GOPROXY=https://goproxy.cn,direct
go install go.k6.io/xk6/cmd/xk6@latest
xk6 build --with github.com/grafana/xk6-output-prometheus-remote@latest

步骤 2:写压测脚本 demo_95line.js

import http from 'k6/http';
import { check } from 'k6';
import { Trend } from 'k6/metrics';

// 自定义趋势指标,方便后续在 Grafana 做聚合
const apiTrend = new Trend('api_duration{scenario:default}', true);

export const options = {
  scenarios: {
    default: {
      executor: 'ramping-vus',
      stages: [
        { duration: '30s', target: 50 },
        { duration: '1m', target: 100 },
        { duration: '30s', target: 0 },
      ],
    },
  },
  thresholds: {
    'http_req_duration{scenario:default}': ['p(95)<500'], // 业务 SLA
  },
};

export default function () {
  const r = http.get('https://api.xxx.com/order/detail', {
    tags: { scenario: 'default', api: 'order_detail' },
  });
  check(r, { 'status is 200': (res) => res.status === 200 });
  apiTrend.add(r.timings.duration, { api: 'order_detail' });
}

步骤 3:启动推送

./k6 run demo_95line.js \
  --out prometheus-remote \
  --tag testid=20250628 \
  --tag region=cn-shanghai \
  --tag team=order \
  --summary-trend-stats="avg,min,med,max,p(95),p(99)"

步骤 4:Grafana 查询语句
Panel 类型:Time series
查询 A:

histogram_quantile(0.95,
  sum(rate(k6_http_req_duration_bucket{testid="20250628"}[5m])) by (le,api)
)

Legend:{{api}} P95

查询 B(自定义指标):

histogram_quantile(0.95,
  sum(rate(k6_api_duration_bucket{testid="20250628"}[5m])) by (le,api)
)

步骤 5:告警(可选)
在阿里云 Prometheus 告警规则里加:

- alert: OrderDetailP95Over1s
  expr: histogram_quantile(0.95, sum(rate(k6_http_req_duration_bucket{api="order_detail"}[2m])) by (le)) > 1000
  for: 2m
  annotations:
    summary: "订单详情接口 P95 超过 1s"

拓展思考

  1. 如果公司不让用 Remote Write,而是走 statsd→Prometheus Exporter,怎么保证 95 线精度不丢失?
    答:statsd 默认采样 10%,需把 K6_STATSD_PUSH_INTERVAL 降到 100ms 并开启 K6_STATSD_ENABLE_TAGS,否则高并发下 95 线被稀释。

  2. 多租户场景,如何防止测试指标污染生产 Prometheus?
    答:在 k6 标签里强制带 env=pt,VictoriaMetrics 通过 drop_metrics{env="pt"} 做软隔离,或直接用单独的 VM 单实例, Grafana 数据源通过 tenant_id 隔离。

  3. 面试官追问“P95 突然掉下去”怎么解释?
    答:大概率是后端重启导致连接池清空,RT 瞬间降低,需把 95 线与 99 线、错误率、Pod 重启事件同屏展示,证明不是“性能变好”,而是“样本缺失”。

  4. 国内金融客户要求保留压测原始数据 5 年,如何低成本实现?
    答:VM 做 downsampling 1h:5m,原始数据转存到阿里云 OSS,通过 Grafana 的 Object Storage Datasource 插件回溯,查询速度 5s 内可接受。