如何在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
相关产品推荐
相关产品推荐

