k6自定义指标输出到InfluxDB/Prometheus:从ext.js到Grafana看板实战
1. 项目概述为什么我们需要自定义 Metric 输出做性能压测k6 是个好工具脚本写起来快资源消耗也低。但用久了你会发现它自带的那些内置指标http_req_duration, http_reqs, vus 等有时候真不够用。比如我想监控被测系统里某个特定业务队列的长度变化或者想记录一个自定义的、复合的业务成功率不是简单的HTTP 200就算成功这些需求内置指标都覆盖不了。这时候自定义 Metric指标就派上用场了。k6 允许你通过ext.js扩展机制在脚本里创建自己的指标比如计数器Counter、计量器Gauge、趋势值Trend、速率Rate。这解决了“有数据”的问题。但紧接着下一个问题就来了这些辛辛苦苦定义和收集的自定义指标数据怎么才能持久化并且方便地展示出来呢总不能每次都靠k6 run在控制台看个大概或者导出一堆 JSON 再手动分析吧这就是我们这个配置要解决的核心问题将 k6 脚本中定义的自定义 Metric无缝、可靠地输出到像 InfluxDB 或 Prometheus 这样的时序数据库中。InfluxDB 和 Prometheus 是监控领域的“黄金搭档”尤其是配合 Grafana 做可视化能让你实时、直观地看到压测过程中业务指标和系统性能的联动变化。我见过不少团队压测报告还停留在“平均响应时间XXms”的层面就是因为自定义指标没打通看不到更深层的业务状态。打通这一步你的压测就从“验证系统扛不扛得住”升级到了“洞察业务在压力下的真实行为”。简单说这个配置能让你定义业务视角的指标不再局限于网络层可以定义如orders_processed_total订单处理总数、payment_success_rate支付成功率等。实现数据持久化与集中化所有压测数据包括自定义的统一进入时序数据库方便历史查询和对比。搭建实时监控看板通过 Grafana 连接 InfluxDB/Prometheus制作专业的压测监控大盘实时观察曲线变化。关联分析将自定义业务指标与系统资源指标如 CPU、内存放在同一个时间轴上分析快速定位瓶颈根源。接下来我会拆解整个流程从扩展原理、配置细节到踩坑实录手把手带你实现它。2. 核心原理与方案选型ext.js 与输出适配器在动手之前得先搞清楚 k6 的数据流是怎么走的。这有助于你理解为什么这么配置以及出了问题该从哪儿查。2.1 k6 的扩展机制ext.js 是什么ext.js是 k6 的一个核心特性它允许你用 JavaScript 编写扩展模块来增强 k6 的功能。这些扩展可以创建新的 JavaScript API在 k6 脚本中调用。发起非 HTTP 的请求例如直接连接数据库、消息队列。收集自定义指标。当你在 k6 脚本中通过new Counter()、new Gauge()等方式创建一个自定义指标时你实际上是在使用 k6 运行时内置的指标模块。而ext.js允许你以模块化的方式封装这些指标的创建和更新逻辑使得代码更清晰、可复用。更重要的是对于输出到外部系统ext.js提供了一种更结构化的方式来挂载数据输出钩子。不过直接通过ext.js将数据发送到 InfluxDB/Prometheus 并不是标准做法。更常见的架构是k6 脚本使用 ext.js 定义指标 - k6 输出器Output - 外部数据库。2.2 输出路径解析Metric 的生命周期一个自定义 Metric 在 k6 中的生命周期大致如下定义与更新在 VU虚拟用户脚本中通过ext.js导出的方法或直接使用k6/metrics创建指标并在适当位置如check()之后、事务结束时更新指标值。内部收集k6 运行时周期性地从所有 VU 中聚合这些指标数据。输出阶段k6 提供了多种输出适配器Output这是数据离开 k6、进入外部系统的关键出口。你可以通过--out命令行参数指定输出器例如--out influxdb将指标输出到 InfluxDB。--out prometheus-remote-write将指标输出到支持 Prometheus Remote Write 协议的端点如 Prometheus 本身、VictoriaMetrics或经过适配的 InfluxDB v2。--out json输出到 JSON 文件供后续处理。外部存储与可视化数据到达 InfluxDB/Prometheus 后被持久化存储。最后通过配置 Grafana 数据源查询并可视化这些指标。因此我们的核心配置工作分为两大部分脚本层编写ext.js扩展和 k6 测试脚本正确定义和更新自定义指标。输出层配置正确的 k6 输出参数以及对应的后端数据库InfluxDB/Prometheus的接收端。2.3 方案选型InfluxDB 还是 Prometheus这是两个最流行的时序数据库但在与 k6 搭配时有一些关键区别特性InfluxDB (v1.x)Prometheusk6 原生支持优秀。--out influxdb是官方内置输出器成熟稳定。间接支持。官方提供--out prometheus-remote-write需后端支持该协议。数据模型基于measurement表、tag标签、field字段、time。更灵活一个点可包含多个字段。基于metric name和label标签。每个时间序列是metric_name{labelvalue}的单一值。写入方式HTTP API 直接写入。通常通过 Prometheus Remote Write API 推送或由 Prometheus 主动拉取但 k6 作为短期任务更适合推送。配置复杂度相对简单直接指定 URL、数据库名和认证信息即可。需要配置 Remote Write 接收端对初学者稍复杂。适用场景更适合作为中心化的指标存储特别是需要存储多个相关字段如duration,success同时存在的场景。k6 的云服务也默认集成 InfluxDB。更适合云原生环境与 Kubernetes 和 Service Mesh 集成深度高。如果公司监控栈已是 Prometheus统一接入更有优势。实操心得对于大多数从零开始的压测监控我推荐InfluxDB v1.x。原因很简单k6 对其支持最直接社区案例最多踩坑时容易找到解决方案。Prometheus 的方案更适合已经具备成熟 Prometheus 生态的团队。注意InfluxDB v2.x 的 API 变化较大k6 的--out influxdb主要兼容 v1.x。如果要用 v2可能需要使用--out prometheus-remote-write并配置 InfluxDB v2 的兼容端点这增加了复杂度。3. 实战配置从 ext.js 到 Grafana 看板下面我们以InfluxDB v1.x为目标完成一个完整的配置示例。Prometheus 的方案会在后面补充关键差异点。3.1 环境准备与安装首先确保你有一个运行中的 InfluxDB v1.x 实例。如果你用 Docker可以快速启动一个docker run -d -p 8086:8086 \ -v $PWD/influxdb-data:/var/lib/influxdb \ -e INFLUXDB_DBk6_db \ -e INFLUXDB_ADMIN_USERadmin \ -e INFLUXDB_ADMIN_PASSWORDadmin123 \ influxdb:1.8这个命令创建了一个 InfluxDB 1.8 容器初始化了一个名为k6_db的数据库并设置了管理员凭证。接下来安装 k6。根据你的操作系统参考 官方安装指南 。Mac 用户可以用brew install k6。3.2 编写 ext.js 扩展与测试脚本我们不把逻辑全部堆在主测试脚本里。创建一个extensions目录在里面写我们的扩展。文件extensions/customMetrics.jsimport { Counter, Gauge, Rate, Trend } from k6/metrics; // 1. 定义自定义指标 // 业务订单处理计数器 export const ordersProcessed new Counter(orders_processed_total); // 业务错误计数器用于计算错误率 export const businessErrors new Counter(business_errors_total); // 当前活跃订单数瞬时值 export const activeOrders new Gauge(active_orders); // 订单处理成功率百分比 export const orderSuccessRate new Rate(order_success_rate); // 复杂业务逻辑耗时趋势 export const businessLogicDuration new Trend(business_logic_duration_ms); // 2. 封装一个工具函数用于更新指标 // 这比在测试脚本里散落更新逻辑要清晰得多 export function recordOrderResult(success, processingTimeMs, orderTags {}) { ordersProcessed.add(1, orderTags); if (success) { orderSuccessRate.add(1, orderTags); } else { businessErrors.add(1, orderTags); orderSuccessRate.add(0, orderTags); } if (processingTimeMs ! undefined) { businessLogicDuration.add(processingTimeMs, orderTags); } } // 3. 可以暴露一个 API 给测试脚本用于模拟订单变化 export function updateActiveOrders(count, orderTags {}) { activeOrders.set(count, orderTags); }文件test_with_custom_metrics.jsimport http from k6/http; import { check, sleep } from k6; // 导入我们自定义的扩展 import { recordOrderResult, updateActiveOrders } from ./extensions/customMetrics.js; export const options { stages: [ { duration: 30s, target: 10 }, { duration: 1m, target: 10 }, { duration: 30s, target: 0 }, ], // 关键配置指定输出到 InfluxDB // 这里先注释运行时通过命令行参数覆盖更灵活 // out: influxdb, }; export default function () { // 模拟一个业务接口 const res http.get(https://httpbin.test.k6.io/status/200); // 使用内置 check 验证 HTTP 层面是否成功 const httpSuccess check(res, { status is 200: (r) r.status 200, }); // 模拟业务逻辑处理时间 (50ms ~ 150ms) const simulatedProcessingTime 50 Math.random() * 100; sleep(simulatedProcessingTime / 1000); // sleep 单位是秒 // *** 核心使用自定义扩展记录业务指标 *** // 假设我们根据更复杂的业务规则判断成功例如返回体包含特定字段 // 这里简化处理将HTTP成功和随机模拟的业务成功结合 const businessSuccess httpSuccess (Math.random() 0.2); // 80% 业务成功率 // 调用扩展方法记录结果。可以添加标签例如按产品线分区 recordOrderResult(businessSuccess, simulatedProcessingTime, { product_line: electronics }); // 模拟活跃订单数的波动 const simulatedActiveOrders Math.floor(Math.random() * 100); updateActiveOrders(simulatedActiveOrders, { product_line: electronics }); }3.3 配置 k6 输出到 InfluxDB现在我们需要告诉 k6 将结果推送到哪里。这通过--out参数和对应的环境变量来完成。方式一通过命令行参数推荐用于本地测试K6_INFLUXDB_USERNAMEadmin \ K6_INFLUXDB_PASSWORDadmin123 \ k6 run \ --out influxdbhttp://localhost:8086/k6_db \ test_with_custom_metrics.js--out influxdb指定输出器类型和 InfluxDB 的 HTTP 写入地址。格式为http://influxdb_host:port/database_name。K6_INFLUXDB_USERNAME和K6_INFLUXDB_PASSWORD如果 InfluxDB 开启了认证则需要设置这些环境变量。我们启动容器时设置了 admin 用户。方式二在脚本 options 中配置你也可以在脚本的options里配置但这样不够灵活通常用于固定环境的场景。export const options { // ... 其他 stages 等配置 ... ext: { loadimpact: { name: My custom test with metrics, // 测试名称会作为 InfluxDB 的 tag }, }, // 配置输出 out: influxdb, };然后在运行时不加--out参数但仍需通过环境变量或修改 k6 源码来指定 InfluxDB 地址这不是最佳实践。更常见的做法是在 CI/CD 或容器环境中通过统一的启动脚本设置--out参数。重要提示k6 的--out influxdb输出器默认会添加一系列标签tags如test_run_id,group,check,scenario等。你自定义指标时添加的标签如上面的product_line会和这些系统标签一起写入 InfluxDB方便进行多维度的筛选和聚合。3.4 在 Grafana 中配置数据源与看板数据已经流入 InfluxDB下一步就是让它“看得见”。启动 Grafanadocker run -d -p 3000:3000 --namegrafana grafana/grafana-oss访问http://localhost:3000默认账号密码admin/admin。添加 InfluxDB 数据源在 Grafana 左侧导航栏点击Configuration (齿轮图标) - Data Sources。点击Add data source选择InfluxDB。配置关键参数URL:http://你的InfluxDB主机:8086(如果 Grafana 容器和 InfluxDB 容器在同一主机且都使用默认桥接网络这里可能是http://host.docker.internal:8086或直接使用宿主机IP。最简单的方式是使用docker network将两者置于同一网络然后用容器名访问如http://influxdb:8086)。Database:k6_dbUser / Password:admin/admin123HTTP Method:GET或POST均可。点击Save Test应显示“Data source is working”。创建压测监控看板点击左侧Dashboards - New dashboard。点击Add visualization。选择你刚配置的 InfluxDB 数据源。在 Query 编辑器中你可以从FROM下拉框里看到 k6 写入的measurement默认是k6。自定义指标会作为field出现在里面。查询示例1绘制订单处理成功率曲线SELECT mean(order_success_rate) FROM k6 WHERE $timeFilter GROUP BY time($__interval), product_line fill(null)选择Visualization为Time series你就能看到成功率随时间变化的曲线并按product_line分组。查询示例2显示当前活跃订单数最新值SELECT last(active_orders) FROM k6 WHERE $timeFilter GROUP BY product_line可以用Stat或Gauge类型的可视化来展示。查询示例3分析业务逻辑耗时分布SELECT mean(business_logic_duration_ms) FROM k6 WHERE $timeFilter GROUP BY time($__interval), product_line可以添加多个查询分别用mean()平均、percentile(95)P95等函数来展示不同分位的耗时。实操心得在 Grafana 中充分利用Tags即 InfluxDB 的 tag进行筛选和分组是制作有效看板的关键。例如你可以创建一个变量Variable$product_line数据源查询SHOW TAG VALUES FROM k6 WITH KEY product_line然后在所有图表的查询中加上AND product_line ~ /^$product_line$/这样就可以动态切换查看不同产品线的压测数据了。4. 进阶输出到 Prometheus 的配置如果你的监控栈是 Prometheus配置思路有所不同。k6 官方提供了prometheus-remote-write输出器。4.1 配置 Prometheus 接收 Remote Write首先需要确保你的 Prometheus 配置了remote_write接收端或者你有一个支持该协议的网关如 Prometheus 本身、VictoriaMetrics或 InfluxDB v2 的 Prometheus 兼容端点。对于 Prometheus 本身需要在prometheus.yml中添加remote_write: - url: http://prometheus-host:9090/api/v1/write # 如果需要认证可以配置 basic_auth 等然后重启 Prometheus。注意Prometheus 默认是拉取模式开启remote_write接收自身写入是一种“自己写给自己”的方式常用于这种推送场景。更常见的做法是使用VictoriaMetrics或Prometheus Remote Storage Adapter它们对远程写入更友好。这里以 VictoriaMetrics 单机版为例Dockerdocker run -d -p 8428:8428 --name victoriametrics victoriametrics/victoria-singleVictoriaMetrics 的远程写入地址是http://localhost:8428/api/v1/write。4.2 配置 k6 使用 prometheus-remote-write 输出运行 k6 脚本时使用以下命令K6_PROMETHEUS_RW_SERVER_URLhttp://localhost:8428/api/v1/write \ K6_PROMETHEUS_RW_USERNAME可选 \ K6_PROMETHEUS_RW_PASSWORD可选 \ k6 run \ --out prometheus-remote-write \ test_with_custom_metrics.js4.3 Prometheus 数据模型差异与注意事项这是最容易出问题的地方指标名称转换k6 的自定义指标名称如orders_processed_total会直接作为 Prometheus 的指标名。Prometheus 要求指标名匹配[a-zA-Z_:][a-zA-Z0-9_:]*正则。k6 的指标名通常符合要求。标签Labelsk6 中的 tags如product_line: electronics会直接转换为 Prometheus 的 labels。非常重要k6 系统自动添加的标签如test_run_id,scenario也会一并写入。这可能导致标签组合数量即时间序列爆炸给 Prometheus 带来压力。需要特别注意。指标类型映射k6Counter- PrometheusCounter(只增不减)k6Gauge- PrometheusGauge(可增可减)k6Rate- PrometheusGauge(因为 Rate 计算出的成功率是一个 0-1 的瞬时值)k6Trend- PrometheusSummary或Histogram这里有个坑k6 的Trend存储了所有值用于计算分位数但通过prometheus-remote-write输出时默认只会输出_sum(总和) 和_count(计数) 两个衍生指标而不是完整的直方图。如果你需要预定义的分位数如 p95需要在 k6 脚本中计算好作为一个单独的Gauge输出。避坑指南使用 Prometheus 方案时务必在压测前后监控 Prometheus 的时间序列数量prometheus_tsdb_head_series和内存使用量。一次大规模、高并发的压测如果每个请求都带大量唯一标签可能会产生数百万个临时序列打爆 Prometheus。建议精简自定义标签避免使用高基数的值如用户ID、请求ID作为标签。考虑在 VictoriaMetrics 等支持高基数的时间序列数据库中进行。或者仍然使用--out influxdb写入 InfluxDB然后通过 Telegraf 的prometheus_output插件将数据转发给 Prometheus利用 InfluxDB 作为缓冲。5. 常见问题、排查技巧与性能优化在实际操作中你肯定会遇到各种问题。这里记录了一些典型场景和解决方法。5.1 数据写入失败或无数据症状k6 运行正常但 InfluxDB/Grafana 中查不到数据。排查步骤检查网络连通性在运行 k6 的机器上用curl或telnet测试能否访问 InfluxDB/Prometheus 的端口如curl -v http://localhost:8086/ping对于 InfluxDB 应返回204 No Content。检查认证信息确保K6_INFLUXDB_USERNAME和K6_INFLUXDB_PASSWORD设置正确且该用户有目标数据库的写权限。对于 InfluxDB可以登录其 CLI (docker exec -it container_id influx) 执行SHOW USERS和GRANT ALL ON k6_db TO username来验证。查看 k6 输出运行 k6 时添加-v或--verbose标志查看是否有关于输出器的错误日志例如WARN[0001] Failed to send samples to InfluxDB ...。查看数据库日志查看 InfluxDB/Prometheus 容器的日志 (docker logs container_name)看是否有拒绝连接、认证失败或写入错误的记录。验证数据库存在对于 InfluxDB确保k6_db数据库已创建。可以通过influxCLI 执行SHOW DATABASES确认。5.2 指标名称或标签在 Grafana 中显示异常症状能看到数据但字段名乱码、标签没显示或查询报错。可能原因与解决特殊字符InfluxDB 的 tag key、tag value、field key 尽量避免使用特殊字符。Prometheus 对指标名和标签名有更严格的限制。建议只使用[a-zA-Z0-9_]。字段类型混淆在 InfluxDB 中同一个 field key 在不同时间点的数据类型必须一致全是浮点数或全是整数。如果你有时写activeOrders.set(10)有时写activeOrders.set(10.5)可能会导致问题。尽量保持类型一致。查询语法错误Grafana 中 InfluxQL 语法要正确。注意SELECT后面跟的是fieldWHERE或GROUP BY后面跟的是tag。使用SHOW FIELD KEYS FROM k6和SHOW TAG KEYS FROM k6来确认数据结构。5.3 压测时输出成为性能瓶颈症状k6 VU 数量很高时测试机本身 CPU/网络很高怀疑是数据写入开销太大。优化策略调整输出批次和间隔k6 的influxdb输出器有缓冲机制。你可以通过环境变量调整K6_INFLUXDB_PUSH_INTERVAL数据推送间隔默认1s。可以适当增大如5s减少请求频率但会增加数据延迟。K6_INFLUXDB_BATCH_SIZE每批发送的数据点数默认1000。可以适当增大如5000但会增加内存占用。 对于 Prometheus remote write也有类似参数K6_PROMETHEUS_RW_PUSH_INTERVAL和K6_PROMETHEUS_RW_MAX_BATCH_SIZE。使用--summary-trend-stats精简输出默认情况下k6 会为所有Trend指标包括内置的http_req_duration计算一堆分位数p90, p95, p99等这会产生大量数据。如果不需要全部可以用--summary-trend-stats avg,p95,max指定只输出平均、P95和最大值。考虑中间缓冲对于极高并发的压测可以考虑先将数据写入到消息队列如 Kafka然后由另一个消费者服务写入到时序数据库。但这套架构就复杂多了。k6 社区有--out kafka的输出器实验性实现可以关注。升级后端数据库确保 InfluxDB/Prometheus 实例配置了足够的内存和磁盘 I/O。对于生产级压测建议使用独立的、资源充足的数据库实例避免和线上监控库混用。5.4 自定义指标值不符合预期症状比如计数器不增长、仪表值不对。调试方法先用--out jsonfile.json验证在命令行运行k6 run --out jsonresults.json your_test.js。运行结束后打开results.json文件搜索你的自定义指标名如orders_processed_total。这个文件包含了 k6 收集的所有原始指标数据。检查其values对象看数据是否正确。这是最直接的调试手段。检查指标更新逻辑确保你在default函数或setup/teardown中正确调用了指标更新方法。注意Gauge的set()是设置瞬时值Counter的add()是累加。不要在init阶段更新指标因为init只执行一次。注意标签匹配在 Grafana 中查询时如果你按标签product_lineelectronics分组但你的脚本里有时用了product_line: electronics有时用了product_line: Electronics大小写敏感它们会被视为两个不同的时间序列。确保标签值的一致性。6. 生产环境部署与最佳实践把这套东西用到 CI/CD 流水线或生产压测中还需要考虑更多。6.1 配置管理不要将数据库连接信息硬编码在脚本或命令行中。建议使用环境变量在 CI/CD 环境如 Jenkins、GitLab CI、GitHub Actions中配置K6_INFLUXDB_URL、K6_INFLUXDB_USERNAME等变量。使用配置文件k6 支持通过--config参数指定一个 JSON 或 JS 配置文件可以在里面定义out选项。但敏感信息密码仍建议通过环境变量传入。封装运行脚本编写一个 shell 脚本或 Makefile统一管理启动命令和参数。6.2 数据隔离与清理每次压测都会产生大量数据。需要做好管理利用 InfluxDB 保留策略Retention Policy, RP为 k6 数据库设置一个合理的 RP例如只保留 30 天数据CREATE RETENTION POLICY k6_30days ON k6_db DURATION 30d REPLICATION 1 DEFAULT。使用不同的 Measurement 或 Tag 区分测试k6 输出的 measurement 默认是k6。你可以通过设置ext.loadimpact.name来改变一个 Tagtest_name的值从而在同一个 measurement 里区分不同测试。或者更彻底的方式是为每次压测使用不同的数据库名通过--out influxdb.../db_name指定。压测后清理对于临时性的、验证性的压测可以在流水线最后增加一个步骤调用 InfluxDB API 删除本次测试产生的数据。6.3 Grafana 看板模板化为不同类型的压测API 压测、业务场景压测创建不同的 Grafana 看板模板并保存为 JSON 文件。在流水线中可以通过 Grafana API 自动创建或更新看板并关联到当次压测的数据源。这样每次压测都能立即获得一个专业的可视化报告。6.4 监控压测过程本身别忘了监控你的 k6 压测执行机。如果压测机资源CPU、内存、网络耗尽测试结果将不可信。可以考虑在压测机上运行 Node Exporter。配置另一个 Prometheus 来抓取压测机的指标。在同一个 Grafana 中创建一个“压测基础设施健康度”看板监控压测机的状态。最后这套自定义指标输出方案将 k6 从一个单纯的“压力发生器”变成了一个“业务可观测性数据生成器”。它的价值不仅在于给出一个通过/不通过的结论更在于提供了压力下系统内部状态的连续、细致的画像。当你能够把“用户登录成功率”的下降和“数据库连接池等待时间”的飙升在时间线上完美对应起来时解决问题的路径就无比清晰了。这才是现代性能工程该有的样子。