浅谈Koa服务限流方法实践


Posted in Javascript onOctober 23, 2017

最近接了一个需求,很简单,就是起一个server,收到请求时调用某个提供好的接口,然后把结果返回。因为这个接口的性能问题,同时在请求的不能超过特定数目,要在服务中进行限流。

限流的要求是,限制同时执行的数目,超出这个数目后要在一个队列中进行缓存。

koa 中间件不调用 next

最初的想法是在 koa 中间件中进行计数,超过6个时将next函数缓存下来。等正在进行中的任务结束时,调用next继续其他请求。

之后发现 koa 中间件中,不执行next函数请求并不会停下,而是不再调用之后的中间件,直接返回内容。

const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
 console.log('middleware 1');
 setTimeout(() => {
  next();
 }, 3000);
 ctx.body = 'hello';
});
app.use((ctx, next) => {
 console.log('middleware 2');
});
app.listen(8989);

以上代码首先在控制台打出 ‘middleware 1' => 浏览器收到 ‘hello' => 控制台打出 ‘middleware 2'。

这里还有一个要注意的地方,就是一个请求已经结束(finish)后,他的next方法还是可以继续调用,之后的middleware还是继续运行的(但是对ctx的修改不会生效,因为请求已经返回了)。同样,关闭的请求(close)也是同样的表现。

使用 await 让请求进行等待

延迟next函数执行不能达到目的。接下来自然想到的就是使用await让当前请求等待。await的函数返回一个Promise,我们将这个Promise中的resolve函数存储到队列中,延迟调用。

const Koa = require('koa');
const app = new Koa();
const queue = [];
app.use(async (ctx, next) => {
 setTimeout(() => {
  queue.shift()();
 }, 3000);
 await delay();
 ctx.body = 'hello';
});
function delay() {
 return new Promise((resolve, reject) => {
  queue.push(resolve);
 });
}
app.listen(8989);

上面这段代码,在delay函数中返回一个Promise,Promise的resolve函数存入队列中。设置定时3s后执行队列中的resolve函数,使请求继续执行。

针对路由进行限流,还是针对请求进行限流?

限流的基本原理实现后,下面一个问题就是限流代码该写在哪里?基本上,有两个位置:

针对接口进行限流

由于我们的需求中,限流是因为要请求接口的性能有限。所以我们可以单独针对这个请求进行限流:

async function requestSomeApi() {
 // 如果已经超过了最大并发
 if (counter > maxAllowedRequest) {
  await delay();
 }
 counter++;
 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) {
 ctx.body = 'error message';
 return;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery 删除或是清空某个HTML元素示例
Aug 04 Javascript
在jQuery中使用$而避免跟其它库产生冲突的方法
Aug 13 Javascript
JS遍历数组和对象的区别及递归遍历对象、数组、属性的方法详解
Jun 14 Javascript
JS仿hao123导航页面图片轮播效果
Sep 01 Javascript
纯js实现倒计时功能
Jan 06 Javascript
Input文本框随着输入内容多少自动延伸的实现
Feb 15 Javascript
jQuery实现遍历复选框的方法示例
Mar 06 Javascript
利用JQUERY实现多个AJAX请求等待的实例
Dec 14 jQuery
深入理解Vue 组件之间传值
Aug 16 Javascript
详解在Vue.js编写更好的v-for循环的6种技巧
Apr 14 Javascript
angular组件间传值测试的方法详解
May 07 Javascript
vue 通过 Prop 向子组件传递数据的实现方法
Oct 30 Javascript
浅谈vue中使用图片懒加载vue-lazyload插件详细指南
Oct 23 #Javascript
angularjs实现猜大小功能
Oct 23 #Javascript
详解在vue-cli项目中使用mockjs(请求数据删除数据)
Oct 23 #Javascript
angularjs实现天气预报功能
Jun 16 #Javascript
angular.js实现购物车功能
Oct 23 #Javascript
使用store来优化React组件的方法
Oct 23 #Javascript
node文件批量重命名的方法示例
Oct 23 #Javascript
You might like
第四节--构造函数和析构函数
2006/11/16 PHP
浅析return false的正确使用
2013/11/04 Javascript
js获取指定的cookie的具体实现
2014/02/20 Javascript
js实现图片和链接文字同步切换特效的方法
2015/02/20 Javascript
使用EVAL处理jqchart jquery 折线图返回数据无效的解决办法
2015/11/26 Javascript
javascript实现网页中涉及的简易运动(改变宽高、透明度、位置)
2015/11/29 Javascript
javascript实现获取浏览器版本、浏览器类型
2015/12/02 Javascript
浅谈js中子页面父页面方法 变量相互调用
2016/08/04 Javascript
AngularJs Managing Service Dependencies详解
2016/09/02 Javascript
JS中如何实现复选框全选功能
2016/12/19 Javascript
关于axios返回空对象的问题解决
2017/04/04 Javascript
微信JSAPI Ticket接口签名详解
2020/06/28 Javascript
微信小程序实现简单评论功能
2018/11/28 Javascript
JavaScript变速动画函数封装添加任意多个属性
2019/04/03 Javascript
layui实现数据表格点击搜索功能
2020/03/26 Javascript
解析vue、angular深度作用选择器
2019/09/11 Javascript
[33:28]完美世界DOTA2联赛PWL S3 PXG vs GXR 第三场 12.19
2020/12/24 DOTA
[01:08:24]DOTA2-DPC中国联赛 正赛 RNG vs Phoenix BO3 第一场 2月5日
2021/03/11 DOTA
Python接收Gmail新邮件并发送到gtalk的方法
2015/03/10 Python
常见的在Python中实现单例模式的三种方法
2015/04/08 Python
Python中的列表知识点汇总
2015/04/14 Python
Perl中著名的Schwartzian转换问题解决实现
2015/06/02 Python
python微信跳一跳系列之棋子定位颜色识别
2018/02/26 Python
opencv python 图片读取与显示图片窗口未响应问题的解决
2020/04/24 Python
新西兰最大、占有率最高的综合性药房:PharmacyDirect药房中文网
2020/11/03 全球购物
如何手工释放资源
2013/12/15 面试题
如何编写优秀的食品项目创业计划书
2014/01/23 职场文书
信息技术教学反思
2014/02/12 职场文书
班级学习雷锋活动总结
2014/07/04 职场文书
低碳环保演讲稿
2014/08/28 职场文书
初中差生评语
2014/12/29 职场文书
教师个人师德总结
2015/02/06 职场文书
北京爱情故事观后感
2015/06/12 职场文书
和谐拯救危机观后感
2015/06/15 职场文书
2016年12月份红领巾广播稿
2015/12/21 职场文书
致创业的您:这类人不适合餐饮创业
2019/08/19 职场文书