
本文介绍在 k6 性能测试中,如何让所有 vu 共享同一份访问令牌(token),并在其过期前统一刷新,避免各 vu 独立申请导致状态不一致;核心方案是借助 redis 作为跨 vu 的共享状态中间件。
本文介绍在 k6 性能测试中,如何让所有 vu 共享同一份访问令牌(token),并在其过期前统一刷新,避免各 vu 独立申请导致状态不一致;核心方案是借助 redis 作为跨 vu 的共享状态中间件。
在 k6 中,每个虚拟用户(VU)运行在隔离的 JavaScript 运行时环境中,无法直接共享内存变量或同步状态。这意味着 setup() 中获取的 token 仅对初始化该 VU 的上下文有效,且后续无法广播更新——这也是用户遇到“1 小时后无法统一刷新 token”的根本原因。
要突破这一限制,必须引入外部协调服务。k6 官方提供的实验性模块 k6/x/redis 是目前最轻量、最契合的解决方案:它允许 VU 通过 Redis 读写共享键值,实现跨 VU 的状态同步与协调。
✅ 推荐架构:双场景协同模式
我们将测试拆分为两个逻辑独立但语义协同的场景:
-
token-manager 场景:仅启动 1 个 VU,周期性(如每 55 分钟)调用认证接口刷新 token,并将新 token 写入 Redis(如 SET token
); - main-test 场景:承载全部压测流量的 VUs,每次请求前从 Redis 读取最新 token(GET token),并配合本地缓存与过期检查,减少阻塞与冗余调用。
⚠️ 注意:redisClient.get() 默认会阻塞当前 VU 直到 key 存在,因此需确保 token-manager 场景先于 main-test 启动(通过 startTime 控制),或添加超时与降级逻辑。
? 示例代码(k6 script)
import { SharedArray } from 'k6/data';
import { sleep, check } from 'k6';
import redis from 'k6/x/redis';
// 初始化 Redis 客户端(需提前运行 Redis 服务,如 docker run -d -p 6379:6379 redis)
const redisClient = redis.connect('redis://localhost:6379');
// 共享配置(可提取至环境变量)
const AUTH_URL = __ENV.AUTH_URL || 'https://api.example.com/auth/token';
const API_BASE = __ENV.API_BASE || 'https://api.example.com/v1';
export const options = {
scenarios: {
// 场景 1:Token 管理器(单 VU,长期运行)
'token-manager': {
executor: 'constant-vus',
vus: 1,
duration: '2h',
startTime: '0s', // 优先启动
exec: 'manageToken',
},
// 场景 2:主压测流量(多 VU,并发执行)
'main-test': {
executor: 'ramping-vus',
startVUs: 10,
stages: [
{ duration: '30s', target: 50 },
{ duration: '5m', target: 50 },
{ duration: '30s', target: 10 },
],
startTime: '10s', // 延迟启动,确保 token 已就绪
exec: 'callProtectedAPI',
},
},
};
// 场景 1:定期刷新并写入 Redis
export function manageToken() {
while (__ENV.TEST_DURATION && new Date() < new Date(Date.now() + 2 * 60 * 60 * 1000)) {
try {
const res = http.post(AUTH_URL, JSON.stringify({ grant_type: 'client_credentials' }), {
headers: { 'Content-Type': 'application/json' },
});
if (check(res, { 'auth token received': (r) => r.status === 200 && r.json().access_token })) {
const token = res.json().access_token;
redisClient.set('token', token);
console.log(`[TOKEN-MANAGER] Updated global token (expires in ~1h)`);
}
} catch (e) {
console.error('[TOKEN-MANAGER] Failed to refresh token:', e.message);
}
sleep(55 * 60); // 每 55 分钟刷新一次,预留缓冲时间
}
}
// 场景 2:业务请求,从 Redis 获取 token
export function callProtectedAPI() {
let token;
try {
token = redisClient.get('token');
if (!token) throw new Error('token not available in Redis');
} catch (e) {
throw new Error(`Failed to fetch token from Redis: ${e.message}`);
}
const res = http.get(`${API_BASE}/users`, {
headers: { Authorization: `Bearer ${token}` },
});
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 800ms': (r) => r.timings.duration < 800,
});
}? 关键实践建议
-
Redis 部署推荐:使用 Docker 快速启动:
docker run -d --name k6-redis -p 6379:6379 -e REDIS_PASSWORD="" redis:alpine
-
健壮性增强:
- 在 callProtectedAPI 中加入 token 本地缓存(如用 SharedArray 存储最近获取值 + 时间戳),减少高频 Redis 调用;
- 对 redisClient.get() 添加 try/catch 与 fallback 重试机制(例如失败后 sleep 1s 再试,最多 3 次);
- 认证接口应支持无状态刷新(如传 refresh_token 或支持 client_credentials 重发),避免依赖 session。
- 可观测性:在 manageToken 中记录日志(如 console.log)并结合 k6 的 --out json=report.json 输出,便于追踪 token 更新时序。
✅ 总结
k6 原生不支持 VU 间变量共享,但通过 k6/x/redis 模块桥接外部存储,即可优雅实现全局状态同步。本方案以低耦合、高可控的方式解决了“统一 token 分发与定时刷新”这一典型企业级测试需求——无需修改被测服务,不增加客户端逻辑,且完全兼容 k6 的分布式执行模型。只要确保 Redis 可达且 token 管理逻辑幂等,即可稳定支撑数小时乃至跨天的长稳测试任务。










