Node.js 由于每秒上限而限制和排队API请求

Node.js 由于每秒上限而限制和排队API请求,node.js,asynchronous,request,throttling,Node.js,Asynchronous,Request,Throttling,我习惯于进行API调用。我最常用的API之一(Shopify API)。最近发布了一个新的,我看到的错误如下: Exceeded 6.0 calls per second for api client. Slow your requests or contact support for higher limits. 我已经得到了升级,但不管我得到多少带宽,我必须对此负责。对Shopify API的大部分请求都在函数内,函数循环异步请求并收集主体 我正在寻找任何帮助,可能是一个已经存在的库,它将

我习惯于进行API调用。我最常用的API之一(Shopify API)。最近发布了一个新的,我看到的错误如下:

Exceeded 6.0 calls per second for api client. Slow your requests or contact support for higher limits.
我已经得到了升级,但不管我得到多少带宽,我必须对此负责。对Shopify API的大部分请求都在函数内,函数循环异步请求并收集主体

我正在寻找任何帮助,可能是一个已经存在的库,它将围绕请求模块,实际阻止、睡眠、调节、分配、管理异步启动的多个并发请求,并将它们限制为一次发出
6
请求。如果这样一个项目不存在,我对它的工作没有问题。我只是不知道如何处理这种情况,我希望有某种标准


我用做了一张罚单。

我在各种API中遇到了相同的问题。AWS也以节流而闻名

可以使用几种方法。您提到了async.map()函数。你试过了吗?queue方法应该允许您设置一个固定限制(如6),超过该限制的任何内容都将放入队列中

另一个有用的工具是。如果从服务器返回错误并重试,该库将允许您回退请求


包装这两个库以确保两个库都被覆盖是很有用的:async.queue以确保您不超过限制,oibackoff以确保在服务器告诉您有错误时,您可以再次获得请求。对于另一种解决方案,我使用了如下包装请求函数:

var request = require('request');
var RateLimiter = require('limiter').RateLimiter;

var limiter = new RateLimiter(1, 100); // at most 1 request every 100 ms
var throttledRequest = function() {
    var requestArgs = arguments;
    limiter.removeTokens(1, function() {
        request.apply(this, requestArgs);
    });
};

下面是我的解决方案,使用库
请求承诺
axios
并将调用封装在这个承诺中

var Promise = require("bluebird")

// http://stackoverflow.com/questions/28459812/way-to-provide-this-to-the-global-scope#28459875
// http://stackoverflow.com/questions/27561158/timed-promise-queue-throttle

module.exports = promiseDebounce

function promiseDebounce(fn, delay, count) {
  var working = 0, queue = [];
  function work() {
    if ((queue.length === 0) || (working === count)) return;
    working++;
    Promise.delay(delay).tap(function () { working--; }).then(work);
    var next = queue.shift();
    next[2](fn.apply(next[0], next[1]));
  }
  return function debounced() {
    var args = arguments;
    return new Promise(function(resolve){
      queue.push([this, args, resolve]);
      if (working < count) work();
    }.bind(this));
  }
var Promise=require(“蓝鸟”)
// http://stackoverflow.com/questions/28459812/way-to-provide-this-to-the-global-scope#28459875
// http://stackoverflow.com/questions/27561158/timed-promise-queue-throttle
module.exports=promisedbounce
功能承诺(fn、延迟、计数){
var working=0,queue=[];
职能工作(){
if((queue.length==0)| |(working==count))返回;
工作++;
delay(delay).tap(函数(){working--;}).then(work);
var next=queue.shift();
next[2](fn.apply(next[0],next[1]);
}
返回函数去Bounced(){
var args=参数;
返回新承诺(函数(解析){
push([this,args,resolve]);
如果(工作<计数)工作();
}.约束(这个);
}

npm
包似乎是解决这个问题的一个很好的方法

此外,它比
节点速率限制器
异步队列
更易于使用

下面是一个片段,演示如何将所有请求限制为每秒10个

var limit = require("simple-rate-limiter");
var request = limit(require("request")).to(10).per(1000);

在异步模块中,此请求的功能被关闭为“不会修复”

  • 2016年给出的理由是“正确管理此类结构是必要的 这是一个难题。”请参见此处右侧:
  • 2013年给出的理由是“无法扩展到多个流程”,见:
有一个解决方案使用漏桶或令牌桶模型,它实现了“限制器”npm模块作为速率限制器

费率限制器
,参见此处示例:

另一种方法是使用PromiseTrottle,我使用了这个,工作示例如下:

var PromiseThrottle = require('promise-throttle');
let RATE_PER_SECOND = 5; // 5 = 5 per second, 0.5 = 1 per every 2 seconds

var pto = new PromiseThrottle({
    requestsPerSecond: RATE_PER_SECOND, // up to 1 request per second
    promiseImplementation: Promise  // the Promise library you are using
});

let timeStart = Date.now();
var myPromiseFunction = function (arg) {
    return new Promise(function (resolve, reject) {
        console.log("myPromiseFunction: " + arg + ", " + (Date.now() - timeStart) / 1000);
        let response = arg;
        return resolve(response);
    });
};

let NUMBER_OF_REQUESTS = 15;
let promiseArray = [];
for (let i = 1; i <= NUMBER_OF_REQUESTS; i++) {
    promiseArray.push(
            pto
            .add(myPromiseFunction.bind(this, i)) // passing am argument using bind()
            );
}

Promise
        .all(promiseArray)
        .then(function (allResponsesArray) { // [1 .. 100]
            console.log("All results: " + allResponsesArray);
        });

我们可以从输出中清楚地看到速率,即每秒5次调用。

其他解决方案不符合我的口味。进一步研究后,我发现它提供了一个api,您可以简单地等待它:

var rate = 2000 // in milliseconds
var throttle = require('promise-ratelimit')(rate)

async function queryExampleApi () {
  await throttle()
  var response = await get('https://api.example.com/stuff')
  return response.body.things
}

上面的示例将确保您最多每2000ms查询一次
api.example.com
。换句话说,第一个请求不会等待2000ms。

使用现代vanilla JS的我的解决方案:

function throttleAsync(fn, wait) {
  let lastRun = 0;

  async function throttled(...args) {
    const currentWait = lastRun + wait - Date.now();
    const shouldRun   = currentWait <= 0;

    if (shouldRun) {
      lastRun = Date.now();
      return await fn(...args);
    } else {
      return await new Promise(function(resolve) {
        setTimeout(function() {
          resolve(throttled());
        }, currentWait);
      });
    }
  }

  return throttled;
}
我使用模块处理HTTP请求。这意味着它允许您以速率限制发送HTTP请求

以下是一个例子:

一个简单的Node.js服务器,在API中添加
express rate limit
中间件,使API具有速率限制功能。假设这是适用于您案例的Shopify API

server.ts

从“express”导入express;
进口税率限制来自“明示税率限制”;
从“http”导入http;
常数端口=3000;
常数限制器=新的速率限制({
windowMs:1000,
最高:3,
消息:“最大RPS=3”,
});
异步函数createServer():Promise{
常量app=express();
应用程序获取('/place',限制器,(请求,恢复)=>{
res.end('查询位置成功');
});
返回应用程序。侦听(端口,()=>{
log(`服务器正在侦听http://localhost:${port}`);
});
}
if(require.main==模块){
createServer();
}
导出{createServer};
在客户端,我们希望发送并发度为3的HTTP请求,并且在它们之间设置每秒上限。我将客户端代码放在一个测试用例中。所以不要觉得奇怪

server.test.ts

从'async sema'导入{RateLimit};
从“请求-承诺”导入rp;
从“chai”导入{expect};
从“/server”导入{createServer};
从“http”导入http;
描述('20253425',()=>{
let服务器:http.server;
beforeach(异步()=>{
服务器=等待createServer();
});
每次之后((完成)=>{
服务器关闭(完成);
});
它('每秒应限制http请求',异步()=>{
常量url=http://localhost:3000/place';
常数n=10;
const lim=速率限制(3,{时间单位:1000});
常量resArr:string[]=[];
for(设i=0;i{
expect(res.to.be.eq('queryplacesuccess');
});
});
function throttleAsync(fn, wait) {
  let lastRun = 0;

  async function throttled(...args) {
    const currentWait = lastRun + wait - Date.now();
    const shouldRun   = currentWait <= 0;

    if (shouldRun) {
      lastRun = Date.now();
      return await fn(...args);
    } else {
      return await new Promise(function(resolve) {
        setTimeout(function() {
          resolve(throttled());
        }, currentWait);
      });
    }
  }

  return throttled;
}
const throttledRun = throttleAsync(run, 1000);