You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在k6中使用多VU从列表API获取不同ID,避免重复删除报错?

解决多VU压力测试中重复删除ID的问题

问题根源

每个VU(虚拟用户)是完全隔离的执行环境,你在setup里创建的数组会被每个VU复制一份——VU之间无法共享这个数组的状态,所以某个VU标记为已删除的ID,其他VU完全看不到,自然还会选中这些ID。

可行解决方案

1. 提前为每个VU分配专属ID池

在setup阶段一次性获取足够多的ID,按VU数量拆分后分配给每个VU,让每个VU只处理自己专属的ID,彻底避免冲突。

import http from 'k6/http';
import { sleep, vu } from 'k6';

export function setup() {
  // 登录获取token
  const loginRes = http.post('/login', { username: 'test', password: 'test' });
  const token = loginRes.json().token;

  // 获取全量测试用ID
  const listRes = http.get('/api/list', { headers: { Authorization: `Bearer ${token}` } });
  const allIds = listRes.json().data.map(item => item.id);

  // 按VU数量拆分ID列表
  const vuCount = __ENV.VU;
  const idsPerVU = Math.ceil(allIds.length / vuCount);
  return { token, allIds, idsPerVU };
}

export default function(data) {
  const currentVUIndex = vu.idInTest - 1;
  // 计算当前VU的ID范围
  const startIdx = currentVUIndex * data.idsPerVU;
  const endIdx = Math.min(startIdx + data.idsPerVU, data.allIds.length);
  const myIds = data.allIds.slice(startIdx, endIdx);

  if (myIds.length === 0) {
    console.log(`VU ${vu.idInTest} 无分配ID,跳过执行`);
    return;
  }

  // 遍历专属ID执行操作
  for (const id of myIds) {
    http.put(`/api/item/${id}`, { content: `updated-by-vu-${vu.idInTest}` }, {
      headers: { Authorization: `Bearer ${data.token}` }
    });
    http.delete(`/api/item/${id}`, {
      headers: { Authorization: `Bearer ${data.token}` }
    });
    sleep(1);
  }
}

2. 每次获取列表后过滤已删除状态的ID

如果无法提前分配ID,每次调用列表API后,只选择状态为未删除的ID(需要列表API返回资源状态字段,比如is_deleted: false)。

import http from 'k6/http';
import { sleep } from 'k6';

export default function() {
  const loginRes = http.post('/login', { username: 'test', password: 'test' });
  const token = loginRes.json().token;
  const headers = { Authorization: `Bearer ${token}` };

  // 获取列表并过滤未删除ID
  const listRes = http.get('/api/list', { headers });
  const availableIds = listRes.json().data
    .filter(item => !item.is_deleted)
    .map(item => item.id);

  if (availableIds.length === 0) {
    console.log('无可用未删除ID,终止当前VU流程');
    return;
  }

  // 随机选一个可用ID执行操作
  const targetId = availableIds[Math.floor(Math.random() * availableIds.length)];
  http.put(`/api/item/${targetId}`, { content: 'updated' }, { headers });
  http.delete(`/api/item/${targetId}`, { headers });
  sleep(1);
}

3. 给删除API加幂等处理(推荐配合其他方案)

和开发团队沟通,让删除API支持幂等:即使重复调用删除同一个已删除的ID,也返回成功状态(比如200或204)而非报错。这样即使出现重复删除,也不会影响压力测试的结果统计。

4. 用外部共享存储记录已删除ID

如果以上方法都不适用,可借助Redis这类外部存储记录已删除ID。每个VU获取ID后先查Redis,未被标记的才执行操作,操作完成后标记为已删除(需k6 Redis扩展支持)。

import http from 'k6/http';
import { sleep } from 'k6';
import redis from 'k6/x/redis';

const client = redis.newClient('redis://localhost:6379');

export default function() {
  const loginRes = http.post('/login', { username: 'test', password: 'test' });
  const token = loginRes.json().token;
  const headers = { Authorization: `Bearer ${token}` };

  const listRes = http.get('/api/list', { headers });
  const allIds = listRes.json().data.map(item => item.id);

  for (const id of allIds) {
    // 尝试将ID加入已删除集合,仅不存在时返回成功
    const isNew = client.sadd('deleted_ids', id);
    if (isNew === 1) {
      http.put(`/api/item/${id}`, { content: 'updated' }, { headers });
      http.delete(`/api/item/${id}`, { headers });
      break;
    }
  }
  sleep(1);
}

选择建议

  • 若测试环境允许提前准备数据,优先用方案1,逻辑简单且彻底避免冲突;
  • 无法提前分配时,用方案2,依赖API状态字段过滤;
  • 长远来看,给删除API加幂等(方案3)是更健壮的做法;
  • 方案4适合复杂场景,但需额外部署Redis,增加测试复杂度。

内容的提问来源于stack exchange,提问作者ZFC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 12:58:37