前端队列请求设计
前言
原文链接: plums-send-vvh.craft.me/HhzdNNnyZAe…
如果有大量并发请求情况下, 一股脑的将请求抛到服务器是很偷懒的做法
而且如果是调用三方服务, 通常会对qps进行限制
例如之前有业务需要调用三方的图片翻译功能, 但是服务商将接口qps限制到5
这个时候如果同一时间有超出5个以上的请求会导致 超出的部分一直是错误的状态
解法
首先是数据结构, 假设qps是5, 其实就相当于有一个办事厅一次只能接待5个人, 超出的就在外面排队
办事厅完成一个, 后面再进来一个, 这是很符合生活直觉的
这种一边进一边出的场景, 很适合使用队列来处理
class Queue {
constructor() {
this.elements = {};
this.head = 0;
this.tail = 0;
}
// 入队
enqueue(element) {
this.elements[this.tail] = element;
this.tail++;
}
// 出队
dequeue(callback) {
const item = this.elements[this.head];
if(callback) callback(item);
delete this.elements[this.head];
this.head++;
return item;
}
// 队首
peek() {
return this.elements[this.head];
}
// 队列长度
length() {
return this.tail - this.head;
}
// 是否为空
isEmpty() {
return this.length() === 0;
}
}
const queue = new Queue();
queue.enque({...})
以上是一个基础版的队列数据结构,
首先分析一下业务, 由于服务商限制, 前端请求一次只能发送5次因为多余的请求即使发出也会失败
当队列满员后续操作需要阻塞, 这就是前端的 异步任务队列 (可以搜索这个获取相关的知识)
本质的操作是 await + Promise 模拟的阻塞效果
class AsyncTaskQueue {
constructor(maxConcurrentRequests) {
this.maxConcurrentRequests = maxConcurrentRequests; // 最大并发数 qps
this.queue = new Queue(); // 用于存储排队的请求
this.currentRequests = 0; // 当前正在进行的请求数
this.pendingRequests = 0; // 等待中的请求数
}
// 执行请求的函数
async enqueue(requestFn) {
// 如果当前请求数达到最大并发数,排队等待
if (this.currentRequests >= this.maxConcurrentRequests) {
// 增加排队请求计数
this.pendingRequests++;
console.log('达到并发限制,生产者等待');
await new Promise(resolve => {
this.queue.enqueue(resolve); // 将resolve函数存入队列,等待被唤醒
});
}
// 开始请求
this.currentRequests++;
try {
await requestFn({
currentRequests: this.currentRequests,
pendingRequests: this.pendingRequests
}); // 执行请求
} catch (error) {
console.error("请求失败", error);
} finally {
// 请求完成,减少当前请求数
this.currentRequests--;
// 当所有请求完成时,打印队列为空信息
if (this.pendingRequests === 0 && this.queue.length() === 0) {
console.log("队列为空, 等待新的请求");
}
// 如果有排队的请求,唤醒下一个
if (this.queue.length() > 0) {
const resolve = this.queue.dequeue();
resolve(); // 唤醒下一个排队的请求
this.pendingRequests--;
}
}
}
}
// 测试用例
// 模拟异步请求函数
async function mockRequest(id, currentRequests , pendingRequests) {
console.log(`\x1b[32m请求 ${id} 开始 , 正在进行的请求${currentRequests} 当前排队长度 ${pendingRequests} \x1b[0m`); // Green for start (入队)
await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟请求延迟
console.log(`\x1b[31m请求 ${id} 完成\x1b[0m`); // Red for complete (出队)
}
// 创建队列实例,最大并发数为 3
const taskQueue = new AsyncTaskQueue(3);
// 模拟 10 个请求
for (let i = 1; i <= 10; i++) {
taskQueue.enqueue(({currentRequests , pendingRequests}) => mockRequest(i, currentRequests , pendingRequests)); // 添加任务到队列
}
以上这个队列可以实现异步任务队列
只需要设置好并发的数量, 然后向队列中push任务即可, 这个时候就不用考虑如何调度请求的问题, 在内部已经处理好了. 下次遇到类似的需求赶紧试一试吧
总结
数据结构用好 实现一些业务功能真的能起到立竿见影的效果