Запросы 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.
Я уже получил обновление, но независимо от того, какую пропускную способность я получу, я должен это учитывать. Подавляющее большинство запросов к API-интерфейсу Shopify находятся внутри функций async.map(), которые зацикливают асинхронные запросы и собирают тела.
Я ищу любую помощь, возможно, библиотеку, которая уже существует, которая обернула бы модуль запроса и фактически блокировала, спит, регулирует, выделяет, управляет многими одновременными запросами, которые запускаются асинхронно и ограничивают их, чтобы сказать 6
запросы одновременно. У меня нет проблем с работой над таким проектом, если он не существует. Я просто не знаю, как справиться с такой ситуацией, и я надеюсь на какой-то стандарт.
Я сделал билет с помощью mikeal/request.
Ответы
Ответ 1
Я столкнулся с той же проблемой с различными API. AWS славится также дросселированием.
Можно использовать пару подходов. Вы упомянули функцию async.map(). Вы пробовали async.queue()? Метод queue должен позволить вам установить твердый предел (например, 6), и что-нибудь в этом количестве будет помещено в очередь.
Другим полезным инструментом является oibackoff. Эта библиотека позволит вам отменить ваш запрос, если вы получите сообщение об ошибке с сервера и повторите попытку.
Может быть полезно обернуть две библиотеки, чтобы убедиться, что обе ваши базы покрыты: async.queue, чтобы вы не переходили за лимит, и oibackoff, чтобы убедиться, что вы получили еще один шанс получить ваш запрос, если сервер сообщает вам Там была ошибка.
Ответ 2
Для альтернативного решения я использовал 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);
});
};
Ответ 3
Пакет npm
simple-rate-limiter кажется очень хорошим решением этой проблемы.
Более того, его проще использовать, чем node-rate-limiter
и async.queue
.
Вот фрагмент, который показывает, как ограничить все запросы до десяти в секунду.
var limit = require("simple-rate-limiter");
var request = limit(require("request")).to(10).per(1000);
Ответ 4
В асинхронном модуле эта запрошенная функция закрывается как "не исправлять",
Существует решение с использованием модели flowybucket или токена, оно реализовано как "лимитирующий" модуль 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 вызовов на каждую секунду.
Ответ 5
Здесь мое решение использует библиотеку 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));
}
Ответ 6
Мое решение с использованием современного ванильного 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)
Ответ 7
Другие решения были не в моих вкусах. Изучая далее, я нашел обещание-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
}
Вышеприведенный пример гарантирует, что вы будете делать запросы только на api.example.com
каждые 2000 мс. Другими словами, самый первый запрос не будет ждать 2000 мс.