
1) 【一句话结论】高并发下,前端通过请求限流(漏桶/令牌桶算法)+ 队列管理控制并发,结合失败重试、用户快速点击节流,并与后端限流协同,避免队列堆积。
2) 【原理/概念讲解】老师口吻解释:当用户快速连续操作(如滚动、点击“加载更多”)时,会触发大量请求,若服务器处理能力有限,请求会积压在队列中,导致用户等待。前端限流的核心是通过算法控制并发请求数,常见策略及机制:
3) 【对比与适用场景】
| 策略 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| 漏桶 | 请求以固定速率流入队列,队列满则丢弃/等待 | 严格按时间速率,突发请求响应慢 | 需要严格限制速率(如API调用限制) | 可能导致用户等待时间长 |
| 令牌桶 | 维护令牌池,请求消耗令牌,用完等待 | 动态调整,支持突发请求 | 需要灵活控制并发(如用户点击触发多个请求) | 需维护令牌池状态 |
| 失败重试 | 请求失败后从队列移除并重试 | 避免无效请求堆积 | 网络波动或服务器临时故障 | 设置重试次数和间隔 |
| 节流 | 限制短时间内多次触发 | 减少无效请求 | 用户快速连续操作(如滚动) | 节流时间根据业务调整 |
4) 【示例】(伪代码,含队列、失败重试、节流)
const bucket = { capacity: 5, current: 0, lastTime: Date.now() };
const retryQueue = [];
function addRequest(url) {
const now = Date.now();
const elapsed = (now - bucket.lastTime) / 1000;
const maxAdd = Math.floor(elapsed * 5); // 每秒最多5个请求
if (bucket.current + maxAdd < bucket.capacity) {
bucket.current += maxAdd;
bucket.lastTime = now;
return fetch(url)
.then(res => {
bucket.current--; // 成功后减少并发数
})
.catch(err => {
bucket.current--; // 失败后减少并发数
retryQueue.push({ url, attempt: 0 }); // 加入重试队列
});
} else {
return new Promise(resolve => {
setTimeout(() => {
addRequest(url).then(resolve).catch(resolve);
}, (bucket.capacity - bucket.current) * 100);
});
}
}
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}
document.getElementById('loadMore').addEventListener('click', throttle(() => {
const url = '/api/data';
addRequest(url).then(() => {
retryQueue = retryQueue.filter(item => item.url !== url);
});
}, 200));
5) 【面试口播版答案】(约90秒)
“面试官您好,针对高并发请求导致队列过长的问题,核心策略是通过前端请求限流,结合队列管理和失败处理,同时处理用户快速点击。具体来说,我会采用漏桶算法控制并发数,比如限制每秒最多5个请求。原理是,将所有请求视为水滴,以固定速率流入‘水桶’,满则等待,避免服务器积压。同时,请求失败时会从队列移除并重试(比如设置2次重试),避免无效请求堆积。用户快速连续点击时,通过节流函数(比如200ms内只触发一次),减少无效请求。另外,前端限流要与后端协同,比如后端API网关也做限流,形成完整体系,当后端处理能力不足时,可优化为异步处理或缓存。这样既能保证服务器稳定,又能提升用户体验。”
6) 【追问清单】
7) 【常见坑/雷区】