NodeJS 根据每秒上限限制API请求并将其排队

rkttyhzu  于 2022-11-29  发布在  Node.js
关注(0)|答案(9)|浏览(220)

我正在使用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做了一张票。

jogvjijk

jogvjijk1#

对于另一种解决方案,我使用node-rate-limiter来 Package request函数,如下所示:

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

zdwk9cvp2#

npmsimple-rate-limiter似乎是这个问题的一个非常好的解决方案。
而且,它比node-rate-limiterasync.queue更容易使用。
下面的代码片段展示了如何将所有请求限制为每秒10个。

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

zfycwa2u3#

我在各种API中都遇到过同样的问题。AWS也以节流而闻名。
可以使用两种方法。您提到了async.map()函数。您尝试过async.queue()吗?queue方法应该允许您设置一个固定的限制(如6),超过该限制的任何内容都将被放入队列中。
另一个有用的工具是oibackoff,如果从服务器返回错误,该库将允许您回退请求,然后重试。
Package 这两个库以确保覆盖两个库是很有用的:async.queue确保您不会超过限制,oibackoff确保您在服务器告诉您有错误时获得另一次请求机会。

4sup72z8

4sup72z84#

我的解决方案使用现代香草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(...args));
        }, currentWait);
      });
    }
  }

  return throttled;
}

// Usage:

const run = console.log.bind(console);
const throttledRun = throttleAsync(run, 1000);

throttledRun(1); // Will execute immediately.
throttledRun(2); // Will be delayed by 1 second.
throttledRun(3); // Will be delayed by 2 second.
mwecs4sa

mwecs4sa5#

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

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

有一种解决方案使用leakybucket或token bucket模型,它被实现为RateLimiter的“限制器”npm模块。

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次调用。

5q4ezhmt

5q4ezhmt6#

其他的解决方案并不符合我的口味。进一步研究,我发现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
}

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

uubf1zoe

uubf1zoe7#

下面是我的解决方案,使用一个库request-promiseaxios,并将调用 Package 在这个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));
  }
hgtggwj0

hgtggwj08#

我使用async-sema模块处理节流HTTP请求。这意味着它允许你发送带有速率限制的HTTP请求。
以下是一个示例:
一个简单的Node.js服务器,在API中添加express-rate-limit中间件,使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 };

在客户端,**我们希望发送并发数为3的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)
iovurdzv

iovurdzv9#

这里有这么多很棒的选择,这里也是我在我的一个项目中使用的一个。
axios-request-throttle
用法:

import axios from 'axios';
import axiosThrottle from 'axios-request-throttle';

axiosThrottle.use(axios, { requestsPerSecond: 5 });

相关问题