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

时间:2013-11-27 21:29:23

标签: node.js asynchronous request throttling

我正在使用mikeal/request进行API调用。我最频繁使用的API之一(Shopify API)。最近发布了一个新的call limit,我发现错误如:

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

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

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

我用mikeal/request打了一张票。

8 个答案:

答案 0 :(得分:33)

对于替代解决方案,我使用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);
    });
};

答案 1 :(得分:22)

node-rate-limitersimple-rate-limiter似乎是解决此问题的一个非常好的解决方案。

此外,它比async.queuevar limit = require("simple-rate-limiter"); var request = limit(require("request")).to(10).per(1000); 更容易使用。

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

 aTags[i].parent("li") 

答案 2 :(得分:14)

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

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

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

包装两个库以确保你的基础都被覆盖是很有用的:async.queue以确保你没有超过限制,并且oibackoff确保你获得另一个镜头来获取你的请求服务器告诉你有错误。

答案 3 :(得分:8)

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

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

RateLimiter ,请参见此示例: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次调用。

答案 4 :(得分:2)

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

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

答案 5 :(得分:1)

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

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
}

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

答案 6 :(得分:0)

我使用现代香草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;
}

用法:

const throttledRun = throttleAsync(run, 1000);

答案 7 :(得分:0)

我使用async-sema模块处理节流HTTP请求。这意味着它允许您发送具有速率限制的HTTP请求。

这里是一个例子:

一个简单的Node.js服务器,向API添加express-rate-limit中间件,以便该API具有速率限制功能。假设这是您案例的Shopify API。

server.ts

import express from 'express';
import rateLimit from 'express-rate-limit';
import http from 'http';

const port = 3000;
const limiter = new rateLimit({
  windowMs: 1000,
  max: 3,
  message: 'Max RPS = 3',
});

async function createServer(): Promise<http.Server> {
  const app = express();

  app.get('/place', limiter, (req, res) => {
    res.end('Query place success.');
  });

  return app.listen(port, () => {
    console.log(`Server is listening on http://localhost:${port}`);
  });
}

if (require.main === module) {
  createServer();
}

export { createServer };

在客户端,我们要发送HTTP请求,并发= 3,并且它们之间的每秒上限。我将客户端代码放在一个测试用例中。所以不要觉得奇怪。

server.test.ts

import { RateLimit } from 'async-sema';
import rp from 'request-promise';
import { expect } from 'chai';
import { createServer } from './server';
import http from 'http';

describe('20253425', () => {
  let server: http.Server;
  beforeEach(async () => {
    server = await createServer();
  });
  afterEach((done) => {
    server.close(done);
  });
  it('should throttle http request per second', async () => {
    const url = 'http://localhost:3000/place';
    const n = 10;
    const lim = RateLimit(3, { timeUnit: 1000 });

    const resArr: string[] = [];
    for (let i = 0; i < n; i++) {
      await lim();
      const res = await rp(url);
      resArr.push(res);
      console.log(`[${new Date().toLocaleTimeString()}] request ${i + 1}, response: ${res}`);
    }

    expect(resArr).to.have.lengthOf(n);
    resArr.forEach((res) => {
      expect(res).to.be.eq('Query place success.');
    });
  });
});

测试结果,请注意请求时间

  20253425
Server is listening on http://localhost:3000
[8:08:17 PM] request 1, response: Query place success.
[8:08:17 PM] request 2, response: Query place success.
[8:08:17 PM] request 3, response: Query place success.
[8:08:18 PM] request 4, response: Query place success.
[8:08:18 PM] request 5, response: Query place success.
[8:08:18 PM] request 6, response: Query place success.
[8:08:19 PM] request 7, response: Query place success.
[8:08:19 PM] request 8, response: Query place success.
[8:08:19 PM] request 9, response: Query place success.
[8:08:20 PM] request 10, response: Query place success.
    ✓ should throttle http request per second (3017ms)


  1 passing (3s)