简体   繁体   English

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

[英]Throttle and queue up API requests due to per second cap

I'm use mikeal/request to make API calls.我正在使用mikeal/request进行 API 调用。 One of the API's I use most frequently (the Shopify API).我最常使用的 API 之一(Shopify API)。 Recently put out a new call limit , I'm seeing errors like:最近推出了一个新的通话限制,我看到如下错误:

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

I've already gotten an upgrade, but regardless of how much bandwidth I get I have to account for this.我已经升级了,但无论我得到多少带宽,我都必须考虑到这一点。 A large majority of the requests to the Shopify API are within async.map() functions, which loop asynchronous requests, and gather the bodies.大多数对 Shopify API 的请求都在async.map()函数中,该函数循环异步请求并收集主体。

I'm looking for any help, perhaps a library that already exists, that would wrap around the request module and actually block, sleep, throttle, allocate, manage, the many simultaneous requests that are firing off asynchronously and limit them to say 6 requests at a time.我正在寻找任何帮助,也许是一个已经存在的库,它会围绕请求模块并实际阻止、睡眠、限制、分配、管理许多异步触发的同时请求并将它们限制为6请求一次。 I have no problem with working on such a project if it doesn't exist.如果不存在这样的项目,我对从事这样的项目没有任何问题。 I just don't know how to handle this kind of situation, and I'm hoping for some kind of standard.我只是不知道如何处理这种情况,我希望有某种标准。

I made a ticket with mikeal/request .我用mikeal/request开了一张票。

For an alternative solution, I used the node-rate-limiter to wrap the request function like this:对于替代解决方案,我使用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);
    });
};

The npm package simple-rate-limiter seems to be a very good solution to this problem. npmsimple-rate-limiter似乎很好地解决了这个问题。

Moreover, it is easier to use than node-rate-limiter and async.queue .此外,它比node-rate-limiterasync.queue更容易使用。

Here's a snippet that shows how to limit all requests to ten per second.这是一个片段,显示了如何将所有请求限制为每秒 10 个。

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

I've run into the same issue with various APIs.我在使用各种 API 时遇到了同样的问题。 AWS is famous for throttling as well. AWS 也以节流而闻名。

A couple of approaches can be used.可以使用几种方法。 You mentioned async.map() function.你提到了 async.map() 函数。 Have you tried async.queue() ?你试过async.queue()吗? The queue method should allow you to set a solid limit (like 6) and anything over that amount will be placed in the queue. queue 方法应该允许你设置一个固定的限制(比如 6),超过这个数量的任何东西都将被放入队列中。

Another helpful tool is oibackoff .另一个有用的工具是oibackoff That library will allow you to backoff your request if you get an error back from the server and try again.如果您从服务器返回错误并重试,该库将允许您退避您的请求。

It can be useful to wrap the two libraries to make sure both your bases are covered: async.queue to ensure you don't go over the limit, and oibackoff to ensure you get another shot at getting your request in if the server tells you there was an error.包装这两个库以确保覆盖您的两个库会很有用: async.queue 以确保您不会超过限制,以及 oibackoff 以确保您在服务器告诉您时再次尝试获取您的请求有错误。

In async module, this requested feature is closed as "wont fix"在异步模块中,这个请求的功能被关闭为“不会修复”

There is a solution using leakybucket or token bucket model, it is implemented "limiter" npm module as RateLimiter.有一个使用leakybucket或令牌桶模型的解决方案,它实现了“限制器”npm模块作为RateLimiter。

RateLimiter , see example here: https://github.com/caolan/async/issues/1314#issuecomment-263715550 RateLimiter ,请参见此处的示例: https : //github.com/caolan/async/issues/1314#issuecomment-263715550

Another way is using PromiseThrottle , I used this, working example is below:另一种方法是使用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);
        });

Output:输出:

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

We can clearly see the rate from output, ie 5 calls for every second.我们可以清楚地看到输出的速率,即每秒 5 次调用。

My solution using modern vanilla JS:我使用现代香草 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;
}

Usage:用法:

const throttledRun = throttleAsync(run, 1000);

The other solutions were not up to my tastes.其他解决方案不符合我的口味。 Researching further, I found promise-ratelimit which gives you an api that you can simply await :进一步研究,我发现promise-ratelimit为您提供了一个可以简单地await的 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
}

The above example will ensure you only make queries to api.example.com every 2000ms at most .上面的例子将确保你只进行查询,以api.example.com每2000毫秒最多 In other words, the very first request will not wait 2000ms.换句话说,第一个请求不会等待 2000 毫秒。

Here's my solution use a library request-promise or axios and wrap the call in this promise.这是我的解决方案,使用库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));
  }

I use async-sema module handle throttle HTTP request.我使用async-sema模块处理节流 HTTP 请求。 Which means it allow you send HTTP request with a rate limit.这意味着它允许您发送具有速率限制的 HTTP 请求。

Here is an example:下面是一个例子:

A simple Node.js server, add express-rate-limit middleware to API so that the API has rate-limit feature.一个简单的 Node.js 服务器,在 API 中添加express-rate-limit中间件,使 API 具有速率限制功能。 Let's say this is the Shopify API for your case.假设这是您案例的 Shopify API。

server.ts : 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 };

On client-side, we want to send HTTP requests with concurrency = 3 and per second cap between them.在客户端,我们希望以并发 = 3 和每秒上限发送 HTTP 请求。 I put the client-side code inside a test case.我将客户端代码放在测试用例中。 So don't feel weird.所以不要觉得奇怪。

server.test.ts : 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.');
    });
  });
});

Test results, Pay attention to the time of the request测试结果,注意请求的时间

  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)

So many great options here, also here is the one that i am using in one of my projects.这里有很多很棒的选择,这也是我在我的一个项目中使用的一个。

axios-request-throttle axios-request-throttle

Usage:用法:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM