浅谈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 相关文章推荐
javascript知识点收藏
Feb 22 Javascript
在textarea文本域中显示HTML代码的方法
Mar 06 Javascript
jquery 事件冒泡的介绍以及如何阻止事件冒泡
Dec 25 Javascript
js动态创建及移除div的方法
Jun 03 Javascript
jQuery+PHP+MySQL二级联动下拉菜单实例讲解
Oct 27 Javascript
轻松实现js图片预览功能
Jan 18 Javascript
详解Angular2中的编程对象Observable
Sep 17 Javascript
在vue中,v-for的索引index在html中的使用方法
Mar 06 Javascript
更改BootStrap popover的默认样式及popover简单用法
Sep 13 Javascript
原生JS实现随机点名项目的实例代码
Apr 30 Javascript
vue+swiper实现左右滑动的测试题功能
Oct 30 Javascript
React + Threejs + Swiper 实现全景图效果的完整代码
Jun 28 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
修改Zend引擎实现PHP源码加密的原理及实践
2008/04/14 PHP
PHP strip_tags()去除HTML、XML以及PHP的标签介绍
2014/02/18 PHP
laravel5环境隐藏index.php后缀(apache)的方法
2019/10/12 PHP
JavaScript 在线压缩和格式化收藏
2009/01/16 Javascript
extjs grid设置某列背景颜色和字体颜色的实现方法
2010/09/06 Javascript
深入理解javascript学习笔记(一) 编写高质量代码
2012/08/09 Javascript
JavaScript执行效率与性能提升方案
2012/12/21 Javascript
JavaScript中遍历对象的property的3种方法介绍
2014/12/30 Javascript
js实现异步循环实现代码
2016/02/16 Javascript
Bootstrap基本插件学习笔记之轮播幻灯片(23)
2016/12/08 Javascript
js 实现省市区三级联动菜单效果
2017/02/20 Javascript
js return返回多个值,通过对象的属性访问方法
2017/02/21 Javascript
js+html5生成自动排列对话框实例
2017/10/09 Javascript
jquery应用实例分享_实现手风琴特效
2018/02/01 jQuery
Koa 使用小技巧(小结)
2018/10/22 Javascript
vue项目添加多页面配置的步骤详解
2019/05/22 Javascript
vue 路由子组件created和mounted不起作用的解决方法
2019/11/05 Javascript
浅谈Vue3.0新版API之composition-api入坑指南
2020/04/30 Javascript
JavaScrip如果基于url实现图片下载
2020/07/03 Javascript
ant-design-vue中的select选择器,对输入值的进行筛选操作
2020/10/24 Javascript
[34:39]Secret vs VG 2018国际邀请赛淘汰赛BO3 第二场 8.23
2018/08/24 DOTA
Python正则表达式的使用范例详解
2014/08/08 Python
python利用beautifulSoup实现爬虫
2014/09/29 Python
Python中字典的基本知识初步介绍
2015/05/21 Python
python结合opencv实现人脸检测与跟踪
2015/06/08 Python
Python的装饰器模式与面向切面编程详解
2015/06/21 Python
Python探索之静态方法和类方法的区别详解
2017/10/27 Python
Python+Django搭建自己的blog网站
2018/03/13 Python
使用pyinstaller打包PyQt4程序遇到的问题及解决方法
2019/06/24 Python
使用Python轻松完成垃圾分类(基于图像识别)
2019/07/09 Python
社区义诊活动总结
2014/04/30 职场文书
无刑事犯罪记录证明
2014/09/18 职场文书
2014幼儿园保育员工作总结
2014/11/10 职场文书
运动会报道稿大全
2015/07/23 职场文书
国际贸易实训总结
2015/08/03 职场文书
python实现的web监控系统
2021/04/27 Python