Accélérez et mettez en queue les demandes d’API en raison d’un plafond par seconde

J’utilise mikeal / request pour faire des appels API. L’une des API que j’utilise le plus fréquemment (l’API Shopify). Récemment mis en place une nouvelle limite d’appel , je vois des erreurs comme:

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

J’ai déjà eu une mise à niveau, mais peu importe la bande passante, je dois en tenir compte. Une grande majorité des demandes adressées à l’API Shopify se trouvent dans les fonctions async.map () , qui bouclent les requêtes asynchrones et rassemblent les corps.

Je cherche une aide, peut-être une bibliothèque qui existe déjà, qui entourerait le module de requête et bloquerait, mettrait en veille, réduirait, allouerait, gérerait les nombreuses requêtes simultanées qui se déclenchent de manière asynchrone et les limiterait à 6 requêtes. à la fois. Je n’ai aucun problème à travailler sur un tel projet s’il n’existe pas. Je ne sais pas comment gérer ce genre de situation et j’espère une sorte de norme.

J’ai fait un ticket avec mikeal / request .

J’ai rencontré le même problème avec différentes API. AWS est également célèbre pour son étranglement.

Quelques approches peuvent être utilisées. Vous avez mentionné la fonction async.map (). Avez-vous essayé async.queue () ? La méthode de la queue devrait vous permettre de définir une limite solide (comme 6) et tout ce qui dépasse ce montant sera placé dans la queue.

Un autre outil utile est oibackoff . Cette bibliothèque vous permettra de annuler votre demande si vous obtenez une erreur du serveur et réessayez.

Il peut être utile d’emballer les deux bibliothèques pour vous assurer que vos deux bases sont couvertes: async.queue pour vous assurer de ne pas dépasser la limite, et oibackoff pour vous assurer d’avoir une nouvelle chance d’obtenir votre requête si le serveur vous le dit. Il y avait une erreur.

Pour une solution alternative, j’ai utilisé le limiteur de débit de nœud pour envelopper la fonction de requête comme ceci:

 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); }); }; 

Le limiteur de taux simple du paquet npm semble être une très bonne solution à ce problème.

En outre, il est plus facile à utiliser que le node-rate-limiter de node-rate-limiter et async.queue .

Voici un extrait qui montre comment limiter toutes les demandes à dix par seconde.

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

Dans le module asynchrone, cette fonctionnalité demandée est fermée sous la forme “wont fix”

Il existe une solution utilisant le modèle leakybucket ou token bucket, il est implémenté “module limiteur” npm comme RateLimiter.

Voir l’exemple ici: 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); }); 

Sortie:

 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 

Nous pouvons clairement voir le taux de sortie, soit 5 appels pour chaque seconde.

Voici ma solution, utilisez une bibliothèque request-promise ou axios et enveloppez l’appel dans cette promesse.

 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)); } 

Les autres solutions n’étaient pas à la hauteur de mes goûts. En recherchant plus loin, j’ai trouvé promis-ratelimit qui vous donne un api que vous pouvez simplement 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 } 

L’exemple ci-dessus garantira que vous ne ferez que des requêtes sur api.example.com toutes les 2000ms au maximum . En d’autres termes, la toute première demande n’attendra pas 2000ms.