服务端优化: - Socket.IO 配置优化,支持高并发 WebSocket 连接 - 添加 connectTimeout、perMessageDeflate 等参数 压测脚本: - 新增 vote-real-scenario.yaml 真实场景压测配置 - 支持 100人瞬间爆发、每人7票、不同投票速度模拟 - 添加随机延迟函数模拟真实用户行为 - 强制 WebSocket 传输避免 HTTP 连接限制 前端修复: - 修复 PostcardItem、PostcardDisplay 奖项名称硬编码问题 - 组件现在从后端 awards 配置动态获取奖项名称 - 修复 LiveVotingView、AdminControl 传递 awards 数据 - 新增 gala 压测命令到 package.json 测试验证: - 100人并发压测通过,成功率 100% - P95 延迟 0.7ms,远低于 500ms 阈值 - 系统可稳定支持 3.3 倍现场负载
205 lines
5.9 KiB
JavaScript
205 lines
5.9 KiB
JavaScript
/**
|
||
* Artillery 压力测试处理器
|
||
* 用于生成测试数据和自定义逻辑
|
||
*
|
||
* 注意:使用 CommonJS 格式以兼容 Artillery
|
||
*/
|
||
|
||
// 节目ID列表
|
||
const programIds = [
|
||
'p1',
|
||
'p2',
|
||
'p3',
|
||
'p4',
|
||
'p5',
|
||
'p6',
|
||
'p7',
|
||
];
|
||
|
||
// 奖项票种列表 (与 shared/constants 一致)
|
||
const ticketTypes = [
|
||
'creative',
|
||
'visual',
|
||
'atmosphere',
|
||
'performance',
|
||
'teamwork',
|
||
'popularity',
|
||
'potential',
|
||
];
|
||
|
||
// 用户计数器 (确保每个虚拟用户有唯一ID)
|
||
let userCounter = 0;
|
||
|
||
/**
|
||
* 生成唯一用户ID
|
||
*/
|
||
function generateUserId(userContext, events, done) {
|
||
userCounter++;
|
||
const timestamp = Date.now();
|
||
userContext.vars.userId = `stress_user_${timestamp}_${userCounter}`;
|
||
userContext.vars.voteIndex = 0; // 初始化投票索引
|
||
userContext.vars.usedTickets = []; // 已使用的票种
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 随机选择一个节目
|
||
*/
|
||
function selectRandomProgram(userContext, events, done) {
|
||
const randomIndex = Math.floor(Math.random() * programIds.length);
|
||
userContext.vars.selectedProgram = programIds[randomIndex];
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 随机选择一个未使用的票种
|
||
* 符合业务逻辑:每种票只能用一次
|
||
*/
|
||
function selectRandomTicketType(userContext, events, done) {
|
||
const usedTickets = userContext.vars.usedTickets || [];
|
||
const availableTickets = ticketTypes.filter(t => !usedTickets.includes(t));
|
||
|
||
if (availableTickets.length === 0) {
|
||
// 所有票已用完,重置 (用于压测场景)
|
||
userContext.vars.usedTickets = [];
|
||
userContext.vars.selectedTicket = ticketTypes[0];
|
||
} else {
|
||
const randomIndex = Math.floor(Math.random() * availableTickets.length);
|
||
userContext.vars.selectedTicket = availableTickets[randomIndex];
|
||
userContext.vars.usedTickets.push(availableTickets[randomIndex]);
|
||
}
|
||
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 顺序选择票种 (确保不重复)
|
||
*/
|
||
function selectNextTicketType(userContext, events, done) {
|
||
const voteIndex = userContext.vars.voteIndex || 0;
|
||
userContext.vars.selectedTicket = ticketTypes[voteIndex % ticketTypes.length];
|
||
userContext.vars.voteIndex = voteIndex + 1;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 记录投票结果 (用于调试)
|
||
*/
|
||
function logVoteResult(userContext, events, done) {
|
||
const { userId, selectedProgram, selectedTicket } = userContext.vars;
|
||
console.log(`[VOTE] User: ${userId} -> Program: ${selectedProgram} (${selectedTicket})`);
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 生成随机延迟 (模拟真实用户行为)
|
||
*/
|
||
function randomDelay(userContext, events, done) {
|
||
const minDelay = 100; // 最小 100ms
|
||
const maxDelay = 500; // 最大 500ms
|
||
const delay = Math.floor(Math.random() * (maxDelay - minDelay)) + minDelay;
|
||
userContext.vars.thinkTime = delay / 1000; // 转换为秒
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 随机入场延迟 0.1-3秒 (模拟分散入场)
|
||
*/
|
||
function randomEntryDelay(userContext, events, done) {
|
||
const delay = (Math.floor(Math.random() * 30) + 1) / 10; // 0.1 - 3.0 秒
|
||
userContext.vars.entryDelay = delay;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 随机短延迟 0.2-0.8秒 (投票间隔)
|
||
*/
|
||
function randomVoteDelay(userContext, events, done) {
|
||
const delay = (Math.floor(Math.random() * 6) + 2) / 10; // 0.2 - 0.8 秒
|
||
userContext.vars.voteDelay = delay;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 随机入场延迟 0-5秒 (观望用户)
|
||
*/
|
||
function randomLateEntryDelay(userContext, events, done) {
|
||
const delay = Math.floor(Math.random() * 50) / 10; // 0 - 5.0 秒
|
||
userContext.vars.entryDelay = delay;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 顺序选择票种 - 确保每人投完7种不同的票(按顺序)
|
||
* 对应7个奖项:creative, visual, atmosphere, performance, teamwork, popularity, potential
|
||
*/
|
||
function selectSequentialTicket(userContext, events, done) {
|
||
const voteIndex = userContext.vars.voteIndex || 0;
|
||
// 按顺序取票种,确保7票投给不同奖项
|
||
userContext.vars.selectedTicket = ticketTypes[voteIndex % ticketTypes.length];
|
||
userContext.vars.voteIndex = voteIndex + 1;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 正常投票延迟 1-2秒(正常投票者)
|
||
*/
|
||
function randomNormalDelay(userContext, events, done) {
|
||
const delay = (Math.floor(Math.random() * 10) + 10) / 10; // 1.0 - 2.0 秒
|
||
userContext.vars.normalDelay = delay;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 长延迟 5-10秒(观望型用户,先不投)
|
||
*/
|
||
function randomLongDelay(userContext, events, done) {
|
||
const delay = (Math.floor(Math.random() * 50) + 50) / 10; // 5.0 - 10.0 秒
|
||
userContext.vars.longDelay = delay;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 慢速投票延迟 3-5秒(犹豫不决型)
|
||
*/
|
||
function randomSlowDelay(userContext, events, done) {
|
||
const delay = (Math.floor(Math.random() * 20) + 30) / 10; // 3.0 - 5.0 秒
|
||
userContext.vars.slowDelay = delay;
|
||
return done();
|
||
}
|
||
|
||
/**
|
||
* 在连接前打印信息 (调试用)
|
||
*/
|
||
function beforeConnect(requestParams, context, ee, next) {
|
||
console.log(`[CONNECT] Connecting user: ${context.vars.userId}`);
|
||
return next();
|
||
}
|
||
|
||
/**
|
||
* 处理响应 (调试用)
|
||
*/
|
||
function afterResponse(requestParams, response, context, ee, next) {
|
||
if (response.body && response.body.error) {
|
||
console.log(`[ERROR] ${context.vars.userId}: ${response.body.error}`);
|
||
}
|
||
return next();
|
||
}
|
||
|
||
// CommonJS exports
|
||
exports.generateUserId = generateUserId;
|
||
exports.selectRandomProgram = selectRandomProgram;
|
||
exports.selectRandomTicketType = selectRandomTicketType;
|
||
exports.selectNextTicketType = selectNextTicketType;
|
||
exports.selectSequentialTicket = selectSequentialTicket;
|
||
exports.logVoteResult = logVoteResult;
|
||
exports.randomDelay = randomDelay;
|
||
exports.randomEntryDelay = randomEntryDelay;
|
||
exports.randomVoteDelay = randomVoteDelay;
|
||
exports.randomLateEntryDelay = randomLateEntryDelay;
|
||
exports.randomNormalDelay = randomNormalDelay;
|
||
exports.randomLongDelay = randomLongDelay;
|
||
exports.randomSlowDelay = randomSlowDelay;
|
||
exports.beforeConnect = beforeConnect;
|
||
exports.afterResponse = afterResponse;
|