k6 v0.54.0 技术解析:实验性 CSV 模块、云本地执行模式与 ECMAScript 新特性
2026/9/10 6:11:37 网站建设 项目流程

k6 v0.54.0 技术解析:实验性 CSV 模块、云本地执行模式与 ECMAScript 新特性

【免费下载链接】k6A modern load testing tool, using Go and JavaScript项目地址: https://gitcode.com/GitHub_Trending/k6/k6

本文围绕 Grafana k6 v0.54.0 版本的核心变更展开,重点剖析新增的k6/experimental/csv实验性 CSV 数据处理模块、k6 cloud run --local-execution本地执行云测试模式、基于 Sobek 引擎升级带来的 ECMAScript 新特性,以及 gRPC、浏览器、WebSocket 模块的若干改进。读完本文,你将掌握 CSV 模块两套 API(csv.parsecsv.Parser)的完整用法与底层实现原理,理解云命令体系的重构方向,并能快速评估这些 Breaking Changes 对自己脚本与扩展的影响。

版本概览

k6 v0.54.0 是一次功能密度很高的发布,主要包含以下内容:

  • 新增实验性 CSV 模块,提供 Go 底层实现的高效 CSV 解析与流式读取能力;
  • 新增k6 cloud下的本地执行(--local-execution)与脚本上传(k6 cloud upload)命令;
  • Sobek(k6 使用的 Go 版 ECMAScript 实现)升级,带来数字分隔符、BigInt、RegExp dotAll、ES2023 数组方法等新特性;
  • 更新了 logo 与终端启动横幅的品牌形象;
  • 同时包含 4 项 Breaking Changes 与大量 UX 改进、Bug 修复和内部维护工作。

Breaking Changes:升级前必须关注的四项变更

math/big.Intbigint的类型映射调整

本次发布(对应 PR #3913)改变了 Go 类型math/big.Int在 k6 JavaScript 运行时中的映射方式,现在它与新增的 JavaScriptBigInt语义保持一致,统一映射为bigint类型。这对依赖旧映射行为(例如将大整数当作普通 number 使用)的自定义扩展或脚本属于破坏性变更,发布说明中明确提示扩展作者与用户需要检查相关用法。

lib.Minlib.Max从 Go API 中移除

PR #3922 移除了lib.Minlib.Max两个 Go 函数。这是纯 Go 层面的 API 调整,主要影响依赖这两个函数的自定义 k6 扩展,普通 JS 脚本不受影响。如果你维护扩展,需要将相关调用替换为 Go 标准库或自定义实现。

k6/experimental/timers模块正式移除

k6/experimental/timers自 v0.53.0 起已被标记废弃(见 v0.53.0 发布说明),在 v0.54.0 中正式移除。原因是定时器 API(setTimeoutsetIntervalsetImmediateclearTimeoutclearIntervalclearImmediate)已经全局可用,不再需要任何 import。从源码看,稳定的k6/timers模块本身也只是把全局对象上的定时器函数重新暴露出来——timers.go 直接读取Runtime().GlobalObject()上的setTimeoutsetInterval等成员。因此迁移路径很简单:删除import ... from 'k6/experimental/timers'语句,直接使用全局函数即可。

k6/experimental/websocketsbinaryType默认值改为"blob"

PR #3944 将k6/experimental/websocketsbinaryType的默认值从"arraybuffer"调整为"blob",使其符合 WebSocket 规范。这意味着:未显式设置binaryType的脚本,接收二进制消息时得到的将是Blob对象而非ArrayBuffer。如果你的脚本依赖旧默认值,需要在创建 WebSocket 时显式声明ws.binaryType = "arraybuffer"

新特性:实验性 CSV 模块k6/experimental/csv

为什么需要它

此前 k6 用户解析 CSV 只能依赖纯 JavaScript 方案(如 papaparse)。这类方案存在两个痛点:解析完全跑在 JS 运行时中,大文件速度慢;且数据只能放在单个 VU 的 JS 堆里,多 VU 间无法共享。v0.54.0 新增的实验性 CSV 模块(对应 PR #3743)用 Go 实现底层解析,直接对接k6/experimental/fs的文件句柄,并提供两种互补的解析策略:

API模式内存占用速度适用场景
csv.parse()全量解析为 SharedArray较高(整个文件常驻共享内存)快(绕过大部分 JS 运行时)追求性能、数据可整体装载
csv.Parsernext()逐行流式读取低(每次只保留一行)相对慢(每行有读取开销)内存敏感、需要精细控制解析过程

两种方式都通过fs.open获得文件句柄,因此天然支持跨 VU 共享数据。

csv.parse():一次性全量解析为 SharedArray

csv.parse(file, options)接收一个由k6/experimental/fsopen()返回的文件对象,在init 阶段一次性消费整个文件,返回解析后的SharedArray(来自k6/data模块的数据结构)。仓库中的完整示例见 examples/experimental/csv-parse.js:

import { open } from 'k6/experimental/fs' import csv from 'k6/experimental/csv' import { scenario } from 'k6/execution' export const options = { iterations: 10, } // 在 init 阶段打开并一次性解析整个文件 const file = await open('data.csv'); const csvRecords = await csv.parse(file, { delimiter: ',' }) export default async function() { // csvRecords 是一个 SharedArray,每个元素是一条记录(字段数组) // 借助 scenario.iterationInTest 可以按迭代顺序取记录 console.log(csvRecords[scenario.iterationInTest]) }

配套的示例数据文件为 examples/experimental/data.csv。注意示例中使用了顶层 awaitawait open(...)await csv.parse(...)),这是模块文档推荐的 init 阶段写法。

代价与权衡csv.parse在测试的初始化阶段就把整个文件读入内存,因此大文件会拉长启动时间并抬高内存占用。它适合"性能优先于内存"的场景。从源码实现看,module.go 中的Parse方法会校验参数、构造内部Reader,然后通过data模块的NewSharedArrayFrom在后台 goroutine 中构建共享数组,最终以 Promise 形式返回。

csv.Parser:逐行流式解析

new csv.Parser(file, options)创建一个流式解析器,通过parser.next()逐行读取。next()返回一个迭代器风格的对象:done表示是否还有更多行,value是该行各字段组成的数组。仓库示例见 examples/experimental/csv-parser.js:

import { open } from 'k6/experimental/fs' import csv from 'k6/experimental/csv' export const options = { iterations: 10, } const file = await open('data.csv'); const parser = new csv.Parser(file); export default async function() { const { done, value } = await parser.next(); if (done) { throw new Error("No more rows to read"); } // value 是字符串数组,每个元素是 CSV 记录中的一个字段 console.log(done, value); }

源码中 module.go 的NewParser构造函数做了几处关键约束:

  • 必须在 init 上下文调用csv.Parser构造函数会检查mi.vu.State(),在测试执行阶段(VU 运行时)调用会直接抛错;
  • 第一个参数必须是fs.File实例,否则抛出类型错误;
  • next()内部在独立 goroutine 中调用底层Reader.Read(),遇到io.EOF时返回{done: true},从而形成类似 JS 迭代器的语义。

解析选项详解

CSV 模块的解析行为可通过 options 对象定制。从源码 reader.go 与 module.go 可以整理出完整的选项表:

选项类型默认值说明
delimiterstring,字段分隔符,必须是单个字符,否则报错
skipFirstLinebooleanfalse是否跳过首行(如表头)
fromLinenumber未设置从第 N 行开始读取(0 起)
toLinenumber未设置读到第 N 行停止(含该行)
asObjectsbooleanfalse将每行映射为对象(键为表头列名,值为对应字段值),此时首行被当作表头

选项之间存在若干互斥/校验规则(validateOptions):

  • asObjectsskipFirstLine不能同时开启;
  • asObjectsfromLine > 0不能同时使用;
  • fromLinetoLine必须大于等于 0,且fromLine必须小于toLine
  • 开启asObjects时若文件没有表头,读取会报错;某行字段数与表头列数不一致时同样报错。

底层实现原理

csv.Parsercsv.parse共享同一套 Go 实现。核心是一个包装encoding/csv.Reader的 Reader:

  • NewReaderFrom在构造阶段完成"跳过首行""跳到 fromLine"等预处理,使Read()调用路径尽量简单;
  • Read()逐行调用底层csv.Reader.Read(),并用原子计数器currentLine跟踪行号以支持toLine截断;
  • asObjects开启时,Read()会把字段数组映射为map[string]string(列名 → 值)。

共享内存的关键在 data 模块。csv.parse之所以能返回 SharedArray,是因为它复用了k6/data模块的 NewSharedArrayFrom ——该函数接收实现了RecordReader接口(即Read() (any, error))的对象,在后台把每条记录 JSON 序列化后装入共享数组。注释中明确说明这样做的动机是绕过整套 JS 运行时机制,避免在 JS 层填充共享数组带来的巨大性能开销。同时 module.go 会用"文件路径 + 解析选项"计算一个 SHA-256 哈希作为共享数组名(csv.parse.<hash>),保证同一文件同一配置的解析结果在多个 VU 间只实例化一次。

此外,CSV 模块的入口 module.go 导出两个命名成员:parse(函数)和Parser(构造函数);文件句柄则来自k6/experimental/fs模块,其File结构体通过公开的 ReadSeekStater 接口向其他模块暴露io.Reader/io.Seeker能力,且该接口字段在 JS 侧被隐藏(js:"-"),避免泄漏实现细节——这正是 CSV 模块能在 Go 层直接读取文件的原因。

k6 cloud命令体系演进

v0.53.0 开始把云相关功能收拢到k6 cloud子命令下,v0.54.0 继续深化这一方向,新增了本地执行与文件上传两个能力。

k6 cloud run --local-execution:云测试本地执行

新引入的--local-execution标志允许你在本地机器上执行测试,同时把指标发送到 Grafana Cloud k6

k6 cloud run --local-execution script.js

关键行为(依据 cloud_run.go 的实现):

  • 默认情况下,--local-execution仍会把测试归档(archive)上传到 Grafana Cloud k6;若想跳过上传,加--no-archive-upload
  • 目前该模式的功能与k6 run -o cloud基本等价,后者已被标记为废弃(但短期内不会移除);未来--local-execution会获得k6 run -o cloud不具备的新能力。

本地执行模式专属的其他标志(均只在--local-execution下有效,混用会报InvalidConfig错误):

标志说明
--linger测试结束后保持 API server 存活
--no-usage-report不发送匿名使用统计
--no-archive-upload不把测试归档上传到云服务
--no-cloud-secrets不自动配置云 secret source
--no-cloud-logs不把日志推送到云端

同时,--local-execution--exit-on-running--show-logs互斥(见 preRun)。

从实现看,k6 cloud run在本地执行模式下会复用cmdRun(即k6 run的执行链路):通过loadAndConfigureLocalTest加载测试,用createCloudTest在云端创建测试运行,然后以本地 controller 驱动执行;配置层面强制把输出设为cloud(getCloudRunLocalExecutionConfig 中out := []string{"cloud"})。这解释了"本地跑、云端看指标"的语义。

k6 cloud upload:上传测试文件到云端

新增的k6 cloud upload命令取代了k6 cloud --upload-only标志(后者标记为废弃)。旧实现中uploadOnly的行为是"上传归档后停止,不启动测试运行"——从 cloud.go 可以看到,上传完成后它会打印测试 URL 与 "Archived" 状态并直接返回。新命令把这一流程独立成一级子命令,属于云服务文件处理链路的一次整理。

配套的 UX 改进

  • k6 cloud login增加了 token 校验(PR #3930),登录后能立即获得反馈;
  • 云测试运行中阈值失败时的进程退出码得到修正(PR #3876、#3923),CI 场景下可以依赖退出码判断云测试是否达标;
  • 修复了cloud命令不出现在k6帮助文本中的问题(PR #3901)。

gRPC 模块:三项来自社区贡献的更新

discardResponseMessage:丢弃服务端响应消息

新增的调用参数discardResponseMessage(PR #3877、#3820)允许丢弃从服务端接收到的消息:

const resp = client.invoke('main.RouteGuide/GetFeature', req, {discardResponseMessage: true});

它的价值在于:当你不关心响应内容(比如只压测请求吞吐)时,丢弃消息可以减少内存占用和 GC 压力,从而降低测试机负载、让测试结果更稳定。从源码看,参数解析位于 params.go,invoke会把它透传到底层 gRPC 调用(client.go),相关行为在 client_test.go 中有InvokeDiscardResponseMessageAsyncInvokeDiscardResponseMessage等测试用例覆盖。感谢贡献者 @lzakharov。

stream 回调新增meta参数

gRPC 流式调用的回调(如'data'事件)新增第二个参数meta(PR #3801),用于携带原始事件发生的时间戳(例如消息被接收的时刻):

let stream = new grpc.Stream(client, "main.FeatureExplorer/ListFeatures") stream.on('data', function (data, meta) { // 打印消息接收时刻的时间戳 call(meta.ts); });

源码层面,stream.go 的queueMessage在收到消息时记录now := time.Now(),构造一个元数据对象并写入ts字段(timestampMetadata = "ts",Unix 秒),随后连同消息一起传给监听器。这使得回调里可以直接拿到接收时间,而无需在 JS 侧自行计时。感谢贡献者 @cchamplin。

gRPC reflection 允许缺失 file descriptors

PR #3871 放宽了 gRPC reflection 的使用限制:当服务端返回的 file descriptors 不完整时不再报错,提升了与真实世界 gRPC 服务的兼容性。感谢贡献者 @Lordnibbler。

Sobek 升级:新的 ECMAScript 特性

v0.54.0 更新了 Sobek(k6 的 Go 语言 ECMAScript 引擎实现,对应 PR #3899、#3925、#3913),以下新语法与 API 现在可直接在 k6 脚本中使用:

数字字面量分隔符——提高大数字可读性:

const billion = 1_000_000_000

BigInt——表示超出 number 原始类型精度范围的整数:

const huge = BigInt(9007199254740991);

注意:v0.54.0 之前 Go 的math/big.Int映射到其他类型,因此本次bigint类型映射调整对依赖旧行为的扩展是破坏性的(见上文 Breaking Changes)。

RegExp dotAll(s标志)——让.可以匹配换行符:

const str1 = "bar\nexample foo example"; const regex1 = /bar.example/s; console.log(regex1.dotAll); // true

ES2023 数组方法Array.prototype.withtoSplicedtoReversedtoSorted。这些是不可变方法,返回新数组而不修改原数组,例如:

const arr = [3, 1, 2]; const sorted = arr.toSorted(); // [1, 2, 3],arr 不变 const reversed = arr.toReversed(); // [2, 1, 3] const spliced = arr.toSpliced(1, 1); // 从索引 1 删 1 个元素 const withVal = arr.with(0, 9); // [9, 1, 2]

感谢 @shiroyk 同时贡献了数组方法与 BigInt 支持。顺带一提,自 v0.53.0 起 k6 已原生支持 ESM(见 v0.53.0 发布说明),这些语言特性在原生 ESM 语境下工作得更加一致。

浏览器模块:新增setChecked方法

此前设置复选框/单选框的状态需要分别调用check()uncheck()。v0.54.0 新增了setChecked方法(对应 xk6-browser PR #1403),用一个布尔参数即可完成两种状态切换:

await page.setChecked('#checkbox', true); // 勾选复选框 await page.setChecked('#checkbox', false); // 取消勾选

该方法同时覆盖了PageFrameElementHandleLocator四类对象。从仓库源码可以确认其落地情况:映射层在 page_mapping.go、frame_mapping.go、element_handle_mapping.go、locator_mapping.go 中均有对应绑定,核心动作则实现在 element_handle.go 的setChecked方法中,走的是与check/uncheck相同的指针动作(pointer action)链路。

WebSocket 模块向规范靠拢

send支持 ArrayBufferViews

PR #3944 让k6/experimental/websocketssend方法可以直接接收Uint8Array等 ArrayBufferViews,不再需要手动取出.buffer传入。这提升了与基于 WebSocket API 的 JS 库的互操作性(感谢 @pixeldrew 反馈)。

readyState真正成为 number

由于底层 JS 引擎(goja/Sobek)内部机制的原因,readyState在 JS 视角下并不是一个严格意义上的 number,导致一些库的互操作出现异常。PR #3972 修复了这一问题,现在readyState是标准的数值类型(感谢 @dougw-bc 反馈)。结合前文提到的binaryType默认值变更,这一系列改动都是k6/experimental/websockets走向规范兼容、进而转正(stable)的铺垫。

使用数据采集机制重做

PR #3917 与 #3951 重构了 k6 内部的使用数据(usage)采集机制。旧的采集系统只能收集表层信息(VU 数量、k6 版本、加载了哪些内部模块),且结构僵硬、难以扩展。重做后新增了以下上报项:

  • 云测试运行标识:使用 Grafana Cloud 时上报当前测试运行 ID,便于把云端运行与本地运行区分开,未来可针对使用实验性模块的云用户发出警告;
  • 解析文件数与.ts文件数:帮助团队判断用户项目规模以及 TypeScript 支持的采用情况;
  • require的使用:原生 ESM 支持落地后,require/CommonJS 增加了维护复杂度,团队需要评估未来移除require的可行性(考虑到其他运行时仍在支持,这大概率是多年后的事);
  • global的使用:用于判断是否可以移除extendedcompatibility-mode 与base模式之间的差异(相关讨论见 v0.53.0 发布说明中关于global别名globalThis的说明)。

同时,usage 报告发送的端点也做了更新(PR #3945)。这些数据仅用于产品决策,可通过--no-usage-report关闭(该标志在本地执行模式下同样可用)。

其他 UX 改进与 Bug 修复

值得留意的改进包括:

  • SetupTimeout选项增加参数校验(PR #3898);
  • Rate指标不再使用容易混淆的/符号,改为{x} out of {y}形式(PR #3765);
  • 修复optionsnil(如从模块导出但未真正导出)时的 panic(PR #3947);
  • 修复事件系统丢事件导致无限挂起的问题(PR #3968);
  • 浏览器模块修复了 iframe attach panic、page.reload处理 null 响应、click空指针、goroutine 无限等待、browser.close中止 CDP close 请求等一系列问题。

维护与内部改进

工程层面,v0.54.0 将go.mod切换到 Go 1.21 并引入 toolchain 机制(PR #3915),CI 与 Dockerfile 同步升级到 Go 1.23 + Alpine 3.20(PR #3938、#3939);golangci-lint 升级到 1.60.1 并新增fatcontextcanonicalheader两个 linter(PR #3900、#3902);新增testutils.MakeMemMapFs测试辅助工具,方便在测试中模拟文件系统(PR #3933);内部删除了NetTrail类型以简化迭代与数据传输指标的内部实现(PR #3908)。

路线图与迁移提示

v0.52.0 中浏览器模块已从实验性转正为稳定的k6/browser,并全面采用 Async API。仍在k6/experimental/browser上的脚本需要迁移到新模块(详见 v0.53.0 发布说明中的迁移指引与 v0.52.0 相关说明)。结合 v0.53.0 的预告,k6/experimental/tracing计划在 v0.55.0 移除、StatsD 输出也将在 v0.55.0 移除,建议相关用户尽早规划迁移到 jslib 或对应扩展。

小结

v0.54.0 在三个方向上都有实质性进展:数据层上,实验性 CSV 模块用 Go 原生解析 + SharedArray 共享内存解决了大数据集压测的解析瓶颈;云服务层上,--local-executionk6 cloud upload让"本地执行 + 云端观测"的混合工作流成为一等公民;语言层上,Sobek 升级持续拉近 k6 与标准 ECMAScript 的距离。与此同时,四项 Breaking Changes(bigint类型映射、lib.Min/lib.Max移除、timers 模块移除、WebSocketbinaryType默认值)提醒升级用户:在动手升级前,检查自定义扩展与脚本中涉及大整数、定时器 import 和 WebSocket 二进制消息的代码,可以最大程度降低迁移成本。

【免费下载链接】k6A modern load testing tool, using Go and JavaScript项目地址: https://gitcode.com/GitHub_Trending/k6/k6

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询