node.js-由于每秒上限而限制并排队API请求

我使用mikeal / request进行API调用。 我最常使用的API之一(Shopify API)。 最近设置了新的通话限制,我看到了如下错误:

Exceeded 6.0 calls per second for api client. Slow your requests or contact support for higher limits.

我已经进行了升级,但是无论获得多少带宽,我都必须考虑这一点。 对Shopify API的大部分请求都在async.map()函数内,该函数循环异步请求并收集主体。

我正在寻找任何帮助,也许是一个已经存在的库,该库将环绕请求模块,并实际上阻止,睡眠,调节,分配,管理,同时触发的许多同时请求,并将它们限制为6请求 一次。 如果没有这样的项目,我没有问题。 我只是不知道如何处理这种情况,我希望有某种标准。

我用迈克(mikeal)/要求订了票。

ThomasReggi asked 2020-01-20T01:34:51Z
7个解决方案
33 votes

对于替代解决方案,我使用了node-rate-limiter来包装请求函数,如下所示:

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);
    });
};
Dmitry Chornyi answered 2020-01-20T01:35:37Z
22 votes

node-rate-limiter包simple-rate-limiter似乎是解决此问题的一个很好的解决方案。

此外,它比node-rate-limiterasync.queue更易于使用。

这是一个片段,显示如何将所有请求限制为每秒十个。

var limit = require("simple-rate-limiter");
var request = limit(require("request")).to(10).per(1000);
Camilo Sanchez answered 2020-01-20T01:36:06Z
14 votes

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

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

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

包装两个库以确保覆盖了您的两个库,这很有用:async.queue以确保您没有超出限制,而oibackoff以确保如果服务器告诉您,可以再次获得请求 有一个错误。

Dan answered 2020-01-20T01:35:17Z
8 votes

在异步模块中,此请求的功能由于“无法修复”而关闭

  • 2016年给出的理由是“正确管理这种结构是一个难题。”请参见此处右侧:[https://github.com/caolan/async/issues/1314]
  • 2013年给出的原因是“无法扩展到多个流程”,请参阅:[https://github.com/caolan/async/issues/37#issuecomment-14336237]

有一个使用leakybucket或令牌桶模型的解决方案,它被实现为“ Limiter” npm模块作为RateLimiter。

PromiseThrottle,请参见此处的示例:[https://github.com/caolan/async/issues/1314#issuecomment-263715550]

另一种方法是使用PromiseThrottle,我使用了此方法,工作示例如下:

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);
        });

输出:

myPromiseFunction: 1, 0.031
myPromiseFunction: 2, 0.201
myPromiseFunction: 3, 0.401
myPromiseFunction: 4, 0.602
myPromiseFunction: 5, 0.803
myPromiseFunction: 6, 1.003
myPromiseFunction: 7, 1.204
myPromiseFunction: 8, 1.404
myPromiseFunction: 9, 1.605
myPromiseFunction: 10, 1.806
myPromiseFunction: 11, 2.007
myPromiseFunction: 12, 2.208
myPromiseFunction: 13, 2.409
myPromiseFunction: 14, 2.61
myPromiseFunction: 15, 2.811
All results: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

我们可以清楚地从输出中看到速率,即每秒5个呼叫。

Manohar Reddy Poreddy answered 2020-01-20T01:36:58Z
2 votes

这是我的解决方案,使用库request-promiseaxios,并将调用包装在此Promise中。

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));
  }
ThomasReggi answered 2020-01-20T01:37:18Z
2 votes

我的解决方案使用现代香草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();
      retur fn(...args);
    } else {
      return new Promise(function(resolve) {
        setTimeout(function() {
          resolve(throttled());
        }, currentWait);
      });
    }
  }

  return throttled;
}

用法:

const throttledRun = throttleAsync(run, 1000);

编辑:删除了await,因为它是多余的,并且很容易抛出:Redundant use of await on a return value.eslint(no-return-await)

djanowski answered 2020-01-20T01:37:47Z
1 votes

其他解决方案不符合我的口味。 进一步研究,我发现promise-ratelimit为您提供了一个API,您可以简单地api.example.com

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。

mindeavor answered 2020-01-20T01:38:11Z
translate from https://stackoverflow.com:/questions/20253425/throttle-and-queue-up-api-requests-due-to-per-second-cap