node.js 由于每秒上限限制和排队 API 请求
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20253425/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Throttle and queue up API requests due to per second cap
提问by ThomasReggi
I'm use mikeal/requestto make API calls. One of the API's I use most frequently (the Shopify API). Recently put out a new call limit, I'm seeing errors like:
我正在使用mikeal/request进行 API 调用。我最常使用的 API 之一(Shopify API)。最近推出了一个新的通话限制,我看到如下错误:
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 6requests at a time. 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.
我正在寻找任何帮助,也许是一个已经存在的库,它会环绕请求模块并实际阻止、睡眠、节流、分配、管理许多异步触发的并发请求,并限制它们6在一个时间。如果它不存在,我对处理这样的项目没有问题。我只是不知道如何处理这种情况,我希望有某种标准。
I made a ticket with mikeal/request.
我用mikeal/request做了一张票。
采纳答案by Dan
I've run into the same issue with various APIs. AWS is famous for throttling as well.
我在使用各种 API 时遇到了同样的问题。AWS 也以节流而闻名。
A couple of approaches can be used. You mentioned async.map() function. Have you tried 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.
可以使用几种方法。你提到了 async.map() 函数。你试过async.queue()吗?queue 方法应该允许你设置一个固定的限制(比如 6),超过这个数量的任何东西都将被放入队列中。
Another helpful tool is oibackoff. That library will allow you to backoff your request if you get an error back from the server and try again.
另一个有用的工具是oibackoff。如果您从服务器返回错误并重试,该库将允许您退避您的请求。
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 以确保您在服务器告诉您时再次尝试获取您的请求有一个错误。
回答by Dmitry Chornyi
For an alternative solution, I used the node-rate-limiterto 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);
});
};
回答by Camilo Sanchez
The npmpackage simple-rate-limiterseems to be a very good solution to this problem.
该npm包简单的速率限制似乎是一个很好的解决了这个问题。
Moreover, it is easier to use than node-rate-limiterand async.queue.
此外,它比node-rate-limiterand更容易使用async.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);
回答by Manohar Reddy Poreddy
In async module, this requested feature is closed as "wont fix"
在异步模块中,这个请求的功能被关闭为“不会修复”
- Reason given in 2016 is "managing that kind of construct properly is a hard problem." See right side of here: https://github.com/caolan/async/issues/1314
- Reason given in 2013 is "wouldn't scale to multiple processes" See: https://github.com/caolan/async/issues/37#issuecomment-14336237
- 2016 年给出的理由是“正确管理这种结构是一个难题。” 见右侧:https: //github.com/caolan/async/issues/1314
- 2013 年给出的原因是“不会扩展到多个进程”参见:https: //github.com/caolan/async/issues/37#issuecomment-14336237
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, i.e. 5 calls for every second.
我们可以清楚地看到输出的速率,即每秒 5 次调用。
回答by soundly_typed
The other solutions were not up to my tastes. Researching further, I found promise-ratelimitwhich gives you an api that you can simply await:
其他解决方案不符合我的口味。进一步研究,我发现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
}
The above example will ensure you only make queries to api.example.comevery 2000ms at most. In other words, the very first request will notwait 2000ms.
上面的示例将确保您最多api.example.com每 2000 毫秒进行一次查询。换句话说,第一个请求不会等待 2000 毫秒。
回答by djanowski
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 fn(...args);
} else {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(throttled());
}, currentWait);
});
}
}
return throttled;
}
Usage:
用法:
const throttledRun = throttleAsync(run,1000);
EDIT: removed awaitas it is redundant and eslint will throw:
Redundant use of await on a return value.eslint(no-return-await)
编辑:删除,await因为它是多余的,eslint 会抛出:
Redundant use of await on a return value.eslint(no-return-await)
回答by ThomasReggi
Here's my solution use a library request-promiseor axiosand wrap the call in this promise.
这是我的解决方案,使用库request-promise或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));
}
回答by slideshowp2
I use async-semamodule handle throttle HTTP request. Which means it allow you send HTTP request with a rate limit.
我使用async-sema模块处理节流 HTTP 请求。这意味着它允许您发送具有速率限制的 HTTP 请求。
Here is an example:
下面是一个例子:
A simple Node.js server, add express-rate-limitmiddleware to API so that the API has rate-limit feature. Let's say this is the Shopify API for your case.
一个简单的 Node.js 服务器,express-rate-limit向 API添加中间件,使 API 具有速率限制功能。假设这是您案例的 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.I put the client-side code inside a test case. So don't feel weird.
在客户端,我们希望以并发 = 3 和每秒上限发送 HTTP 请求。我将客户端代码放在一个测试用例中。所以不要觉得奇怪。
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)

