Acelere y ponga en cola las solicitudes de API debido al límite por segundo


Utilizo mikeal/request para hacer llamadas a la API. Una de las API que uso con más frecuencia (la API de Shopify). Recientemente lanzó 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 tengo que tener en cuenta para esto. Una gran mayoría de las solicitudes a la API de Shopify están dentro de async.map () funciones, que enlazan peticiones asíncronas, y reúnen los cuerpos.

Estoy buscando para cualquier ayuda, tal vez una biblioteca que ya existe, que envuelva el módulo de solicitud y realmente bloquee, suspenda, acelere, asigne, administre, las muchas solicitudes simultáneas que se disparan asincrónicamente y las limite a decir 6 solicitudes a la vez. No tengo ningún problema con trabajar en un proyecto de este tipo si no existe. Simplemente no sé cómo manejar este tipo de situación, y estoy esperando algún tipo de estándar.

Hice un ticket con mikeal/request.

Author: ThomasReggi, 2013-11-28

6 answers

Me he encontrado con el mismo problema con varias API. AWS también es famoso por la limitación.

Se pueden usar un par de enfoques. Mencionaste async.función map (). Has intentado async.queue () ? El método de cola debería permitirle establecer un límite sólido (como 6) y cualquier cosa que supere esa cantidad se colocará en la cola.

Otra herramienta útil es oibackoff. Esa biblioteca le permitirá retroceder su solicitud si obtiene un error del servidor e intenta nuevo.

Puede ser útil envolver las dos bibliotecas para asegurarse de que sus dos bases estén cubiertas: async.hacer cola para asegurarse de no sobrepasar el límite, y oibackoff para asegurarse de que tiene otra oportunidad de obtener su solicitud si el servidor le dice que hubo un error.

 12
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-07 07:37:02

Para una solución alternativa, usé el node-rate-limiter para envolver la función de solicitud de la siguiente 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);
    });
};
 31
Author: Dmitry Chornyi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-29 18:38:48

El paquete npm simple-rate-limiter parece ser una muy buena solución a este problema.

Además, es más fácil de usar que node-rate-limiter 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);
 18
Author: Camilo Sanchez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-05 12:09:56

En el módulo async, esta característica solicitada se cierra como" wont fix "

Hay una solución que utiliza el modelo leakybucket o token bucket, es implementado el módulo npm "limiter" como RateLimiter.

RateLimiter, ver ejemplo aquí: https://github.com/caolan/async/issues/1314#issuecomment-263715550

Otra forma es usar PromiseThrottle, He utilizado este, ejemplo de trabajo es a continuación:

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.

 5
Author: Manohar Reddy Poreddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-17 03:39:10

Aquí está mi solución use una 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));
  }
 2
Author: ThomasReggi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-17 16:53:09

Las otras soluciones no estaban a mi gusto. Investigando más, encontré promise-ratelimit que te da una api que puedes simplemente 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 asegurará que solo realice consultas a api.example.com cada 2000ms como máximo. En otras palabras, la primera solicitud no esperará 2000ms.

 0
Author: mindeavor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-18 20:08:34