
1) 【一句话结论】
针对微信红包高并发抢发场景,前端通过“请求去重(本地存储唯一标识)、频率控制(防抖)、服务端幂等性配合”三重机制,结合网络异常的指数退避重试,确保请求不重复提交且系统稳定。
2) 【原理/概念讲解】
首先解释“请求去重”的核心:为每个抢红包请求生成唯一标识,前端通过本地存储(如sessionStorage)记录已提交标识,避免同一标识重复发送。类比“快递单号”,每个包裹有唯一编号,系统通过单号判断是否已处理,避免重复投递。接着说明“频率控制”:用户快速点击可能导致多次请求,需用防抖(Debounce)函数控制,连续触发事件后延迟执行(如用户快速点击抢红包按钮,只发送最后一次请求)。再解释“服务端配合”:前端去重是基础,但服务端需实现幂等性(重复请求不产生重复结果),比如通过请求头中的唯一标识判断是否已处理,或用数据库事务保证结果唯一(如将标识存入数据库,若已存在则返回“已抢”状态)。最后补充“网络异常处理”:当请求超时或失败时,采用指数退避策略,随机等待1-30秒后重试,避免请求雪崩。
3) 【对比与适用场景】
对比唯一标识生成策略(时间戳+随机数 vs UUID+时间戳):
| 策略名称 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| 时间戳+随机数 | 请求时生成时间戳+随机数 | 依赖时间戳,易被篡改 | 低并发或对唯一性要求不高 | 需确保时间戳准确性,避免重复 |
| UUID+时间戳 | 请求时生成UUID+时间戳 | UUID全局唯一,时间戳辅助 | 高并发场景,保证唯一性 | UUID生成稍耗时,但前端效率高 |
对比存储方案(localStorage vs sessionStorage):
| 存储方案 | 定义 | 特性 | 适用场景 | 注意点 |
|---|---|---|---|---|
| localStorage | 全局存储,多标签页共享 | 跨标签页共享,持久化 | 单标签页或需跨标签页共享 | 多标签页时标识冲突 |
| sessionStorage | 会话级存储,标签页独立 | 每个标签页独立,会话结束清除 | 多标签页抢红包 | 会话内隔离,避免冲突 |
4) 【示例】
抢红包按钮的点击事件处理(含防抖、sessionStorage、请求头标识、服务端幂等性、指数退避):
// 生成唯一请求标识(UUID+时间戳,更安全)
function generateRequestId() {
return 'uuid' + Date.now().toString() + Math.random().toString(36).substr(2);
}
// 防抖函数,控制点击频率
function debounceClick(func, delay) {
let timer;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
func();
}, delay);
};
}
// 抢红包按钮事件处理
const grabBtn = document.getElementById('grab-btn');
grabBtn.addEventListener('click', debounceClick(() => {
const requestId = generateRequestId();
// 检查sessionStorage是否已有该请求标识(多标签页隔离)
if (sessionStorage.getItem('grab-request-id')) {
console.log('已提交过请求,忽略');
return;
}
// 存储标识,避免重复
sessionStorage.setItem('grab-request-id', requestId);
// 发送请求,携带唯一标识
fetch('/api/grab', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-Id': requestId
},
body: JSON.stringify({ userId, redpackId })
})
.then(res => {
if (res.ok) {
console.log('抢到红包');
sessionStorage.removeItem('grab-request-id'); // 成功后清除标识
} else {
console.log('抢失败');
}
})
.catch(err => {
console.error('请求错误');
// 网络异常后,指数退避重试
retryRequest(requestId);
});
}, 300); // 300ms防抖,避免快速点击
// 指数退避重试函数
function retryRequest(requestId) {
const randomBackoff = Math.min(1000 * Math.pow(2, Math.floor(Math.random() * 5)), 30000); // 1-30秒随机退避
setTimeout(() => {
// 重新发送请求,检查sessionStorage标识(避免重复)
if (!sessionStorage.getItem('grab-request-id')) {
fetch('/api/grab', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-Id': requestId
},
body: JSON.stringify({ userId, redpackId })
})
.then(res => res.ok ? console.log('重试成功') : console.log('重试失败'))
.catch(err => console.error('重试失败'));
}
}, randomBackoff);
}
// 服务端幂等性示例(数据库操作伪代码)
// 假设数据库表redpacks,字段request_id(唯一索引)
// 事务处理:
// BEGIN TRANSACTION;
// INSERT INTO redpacks (request_id, user_id, redpack_id) VALUES (?, ?, ?);
// ON DUPLICATE KEY UPDATE status = 'grabbed'; -- MySQL唯一索引冲突处理
// COMMIT;
5) 【面试口播版答案】
“面试官您好,针对微信红包抢发的高并发场景,前端方案的核心是通过‘请求去重(本地存储唯一标识)、频率控制(防抖)、服务端幂等性配合’三重机制,结合网络异常的指数退避重试,确保请求不重复提交且系统稳定。具体来说,前端为每个抢红包请求生成唯一标识(如UUID+时间戳),存储在sessionStorage中,避免同一标识在多标签页重复发送;同时用300ms防抖函数控制用户快速点击,防止多次请求;请求头携带该标识,服务端通过标识判断是否已处理,实现幂等性(如数据库唯一索引或事务保证结果唯一)。此外,网络异常时采用指数退避策略,超时后随机等待1-30秒再重试,避免请求雪崩;多标签页时,每个标签页使用独立的sessionStorage标识,避免跨标签页冲突;若用户清除缓存,标识丢失后重新生成,确保去重逻辑仍有效。这样前端既解决了重复提交问题,又通过后端配合和异常处理保证了高并发下的稳定性。”
6) 【追问清单】
sessionStorage.setItem('grab-id', tabId + requestId)),不同标签页的sessionStorage独立,标识不冲突。7) 【常见坑/雷区】