
本文介绍在 k6 多虚拟用户(vu)压测场景下,如何通过模运算实现 id 列表的确定性分片,确保每个 vu 只处理互不重叠的 id 子集,从而避免并发删除冲突、重复编辑等竞态问题。
本文介绍在 k6 多虚拟用户(vu)压测场景下,如何通过模运算实现 id 列表的确定性分片,确保每个 vu 只处理互不重叠的 id 子集,从而避免并发删除冲突、重复编辑等竞态问题。
在使用 k6 进行端到端流程压测(如登录 → 获取 ID 列表 → 编辑 → 删除)时,单 VU 下逻辑清晰、执行稳定;但一旦扩展至多个 VU(例如 3 个或更多),若所有 VU 均从同一 API 响应中无差别地随机或顺序选取 ID,则极易出现多个 VU 同时尝试编辑或删除同一个资源——尤其在“删除后不可再删”的 RESTful 接口约束下,将触发 404 或 409 错误,污染压测结果,掩盖真实性能瓶颈。
根本解法不是加锁或共享状态(k6 的 VU 是隔离、无共享内存的轻量协程),而是让每个 VU 主动、确定性地只处理属于自己的那部分 ID。推荐采用基于 VU 编号的模分片策略(mod-based sharding):
假设你已通过 list API 获取完整 ID 数组(如 const ids = response.json().data.map(item => item.id)),则可在后续步骤中按如下方式分配:
export default function () {
// 示例:已获取 ids = [101, 102, 103, 104, 105, 106, 107, 108]
const myIds = ids.filter((id, idx) => id % __ENV.K6_VUS === __VU - 1);
// 注意:此处用 id % VU 总数做分片,而非索引 idx —— 更健壮,因 ID 本身通常为递增整数,天然具备均匀分布性
// 若 ID 非数字,可改用 hash(id) % __ENV.K6_VUS
for (const id of myIds) {
// 执行 edit API
http.put(`https://api.example.com/items/${id}`, JSON.stringify({ status: 'updated' }));
// 执行 delete API
http.delete(`https://api.example.com/items/${id}`);
}
}
⚠️ 关键说明与最佳实践:
-
__VU是当前 VU 的编号(从 1 开始),__ENV.K6_VUS需在启动时显式传入(如k6 run --vus 3 --env K6_VUS=3 script.js),避免依赖options.vus(它在 setup 阶段不可用,且 runtime 中可能未同步更新); - 使用
id % __ENV.K6_VUS === __VU - 1而非idx % __ENV.K6_VUS === __VU - 1,是因为 ID 值本身更可能具备全局唯一性和分布均匀性,而响应数组索引受分页、排序、过滤影响,不稳定; - 若 ID 为字符串(如 UUID),请先哈希:
require('k6/crypto').hash('sha256', id).substr(0, 8) % __ENV.K6_VUS(注意转换为整数); - 此方案完全无状态、无竞争、无额外网络开销,符合 k6 的设计哲学;
- 补充建议:在 setup 函数中预加载并分片 ID 列表(返回
{ myIds: [...] }),再于 default 中消费,可进一步提升复用性与可读性。
通过这种确定性分片,3 个 VU 将严格各司其职:VU1 处理 ID ≡ 0 (mod 3),VU2 处理 ID ≡ 1 (mod 3),VU3 处理 ID ≡ 2 (mod 3),彻底消除跨 VU 冲突,使压测真正反映系统在高并发下的真实吞吐与稳定性。










