浅谈Koa服务限流方法实践

2020-06-17 07:03:18易采站长站整理

const result = await request('http://some.api');
counter--;
queue.shift()();
return result;
}

下面还有一个方便复用的版本。


async function limitWrapper(func, maxAllowedRequest) {
const queue = [];
const counter = 0;
return async function () {
if (counter > maxAllowedRequest) {
await new Promise((resolve, reject) => {
queue.push(resolve);
});
}
counter++;
const result = await func();
counter--;
queue.shift()();
return result;
}
}

针对路由进行限流

这种方式是写一个koa中间件,在中间件中进行限流:


async function limiter(ctx, next) => {
// 如果超过了最大并发数目
if (counter >= maxAllowedRequest) {
// 如果当前队列中已经过长
await new Promise((resolve, reject) => {
queue.push(resolve);
});
}
store.counter++;
await next();
store.counter--;
queue.shift()();
};

之后针对不同路由在router中使用这个中间件就好了:


router.use('/api', rateLimiter);

比较

实现了针对接口进行限流,觉得逻辑有些乱,于是改用了针对路由进行限流,一切运行的很完美。

直到我又接了个需求,是要请求三次这个接口返回三次请求的结果数组。现在问题来了,我们不能直接调用接口,因为要限流。也不能直接调用请求接口的函数因为我们的限流是以路由为单位的。那怎么办呢?我们只有请求这个路由了,自己请求自己。。。

需要注意的地方

监听close事件,将请求从队列中移出
已经存储在队列中的请求,有可能遇到用户取消的情况。前面说过koa中即使请求取消,之后的中间件还是会运行,也就是还会执行需要限流的接口,造成浪费。

可以监听close事件来达到这个目的,每个请求我们需要用hash值来标记:


ctx.res.on('close', () => {
const index = queue.findIndex(item => item.hash === hash);
if (index > -1) {
queue.splice(index, 1);
}
});

设置超时时间

为了防止用户等待过长时间,需要设置超时时间,这在koa中很容易实现:


const server = app.listen(config.port);
server.timeout = DEFAULT_TIMEOUT;

当前队列已经过长

如果当前队列已经过长了,即使加入队列中也会超时。因此我们还需要处理队列过长的情况:


if (queue.length > maxAllowedRequest) {