node.js - una - Acelerar y hacer cola en las solicitudes API por límite por segundo
mongodb node js api (6)
Aquí está mi solución, use una request-promise
biblioteca request-promise
o axios
y envuelva la llamada en esta promesa.
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));
}
Estoy usando mikeal/request para hacer llamadas API. Una de las API que uso con más frecuencia (la API de Shopify). Recientemente saqué un nuevo límite de llamadas , estoy viendo errores como:
Exceeded 6.0 calls per second for api client. Slow your requests or contact support for higher limits.
Ya he recibido una actualización, pero independientemente de la cantidad de ancho de banda que tenga, tengo que dar cuenta de esto. Una gran mayoría de las solicitudes a la API de Shopify están dentro de las funciones async.map() , que async.map() bucle de solicitudes asíncronas y reúnen los cuerpos.
Estoy buscando alguna ayuda, tal vez una biblioteca que ya exista, que cubra el módulo de solicitud y realmente bloquee, suspenda, acelere, asigne, administre, las muchas solicitudes simultáneas que se activan de forma asincrónica y limítelas a decir 6
solicitudes a la vez No tengo ningún problema para trabajar en un proyecto como este si no existe. Simplemente no sé cómo manejar este tipo de situación, y espero algún tipo de estándar.
Hice un boleto con mikeal/request .
El npm
simple-rate-limiter paquete npm
parece ser una muy buena solución a este problema.
Además, es más fácil de usar que node-rate-limiter
async.queue
y async.queue
.
Aquí hay un fragmento que muestra cómo limitar todas las solicitudes a diez por segundo.
var limit = require("simple-rate-limiter");
var request = limit(require("request")).to(10).per(1000);
En el módulo asíncrono, esta función solicitada se cierra como "no se soluciona"
- La razón dada en 2016 es "manejar ese tipo de construcción correctamente es un problema difícil". Ver el lado derecho de aquí: https://github.com/caolan/async/issues/1314
- La razón dada en 2013 es "no escalaría a procesos múltiples" Ver: https://github.com/caolan/async/issues/37#issuecomment-14336237
Existe una solución que usa el modelo leakybucket o token bucket, se implementa el módulo "limitador" npm como RateLimiter.
Vea el ejemplo aquí: https://github.com/caolan/async/issues/1314#issuecomment-263715550
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);
});
Salida:
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
Podemos ver claramente la tasa de salida, es decir, 5 llamadas por cada segundo.
Las otras soluciones no estaban a mi gusto. Investigando más, encontré promise-ratelimit que te da una API que simplemente puedes 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
}
El ejemplo anterior garantizará que solo realice consultas a api.example.com
cada 2000 ms como máximo . En otras palabras, la primera solicitud no esperará 2000 ms.
Me encontré con el mismo problema con varias API. AWS es famoso por la aceleración también.
Se pueden usar un par de enfoques. Usted mencionó la función async.map (). ¿Has probado async.queue() ? El método de cola debería permitirle establecer un límite sólido (como 6) y cualquier cantidad superior a esa cantidad se colocará en la cola.
Otra herramienta útil es oibackoff . Esa biblioteca le permitirá retrasar su solicitud si recibe un error del servidor e intenta nuevamente.
Puede ser útil ajustar las dos bibliotecas para asegurarse de que sus dos bases estén cubiertas: async.queue para asegurarse de que no exceda el límite, y oibackoff para asegurarse de tener otra oportunidad para recibir su solicitud si el servidor le dice Hubo un error.
Para una solución alternativa, utilicé el node-rate-limiter para ajustar la función de solicitud de esta manera:
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);
});
};