Дроссель и очередь вверх по запросам API из-за в секунду крышки

Я использую mikeal/запрос для выполнения вызовов API. Один из API, который я использую чаще всего (API Shopify). Недавно выпустили новый ограничение вызова, Я вижу ошибки, как:

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

Я уже получил обновление, но независимо от того, сколько пропускной способности я получаю, я должен учитывать это. Подавляющее большинство запросов к API Shopify находятся в пределах асинхронность.map () функции, которые выполняют циклические асинхронные запросы и собирают тело.

Я ищу любую помощь, возможно, библиотеку, которая уже существует, которая обернется вокруг модуля запроса и фактически заблокирует, спит, дроссель, выделяет, управляет многими одновременными запросами, которые запускаются асинхронно и ограничивают их, чтобы сказать 6 запросов одновременно. У меня нет проблем с работе над таким проектом, если он не существует. Я просто не знаю, как справиться с такой ситуацией, и я надеюсь на какой-то стандарт.

I сделал билет с mikeal/запрос.

6 ответов


Я столкнулся с той же проблемой с различными API. AWS также славится дросселированием.

можно использовать несколько подходов. Вы упомянули асинхронность.карте (функция). Вы пробовали асинхронность.queue ()? Метод очереди должен позволить вам установить твердый предел (например, 6), и все, что превышает эту сумму, будет помещено в очередь.

еще один полезный инструмент oibackoff. Эта библиотека позволит вам отменить запрос, если вы получите сообщение об ошибке с сервера и повторите попытку.

может быть полезно обернуть две библиотеки, чтобы убедиться, что обе ваши базы покрыты: async.очередь, чтобы убедиться, что вы не превышаете лимит, и oibackoff, чтобы убедиться, что вы получите еще один шанс на получение вашего запроса, если сервер скажет вам, что произошла ошибка.


альтернативного решения, я использовал узел-скорость-ограничитель чтобы обернуть функцию запроса, как это:

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

на npm пакета простой-скорость-ограничитель кажется очень хорошим решением этой проблемы.

кроме того, это проще в использовании, чем node-rate-limiter и async.queue.

вот фрагмент кода, который показывает, как ограничить все запросы до десяти в секунду.

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

в асинхронном модуле эта запрошенная функция закрыта как "wont fix"

  • причина, приведенная в 2016 году, заключается в том, что "управление такой конструкцией правильно трудная проблема."Смотрите правую сторону здесь: https://github.com/caolan/async/issues/1314
  • причина, приведенная в 2013 году, "не будет масштабироваться до нескольких процессов" см.: https://github.com/caolan/async/issues/37#issuecomment-14336237

существует решение, использующее модель leakybucket или token bucket, реализован модуль 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 вызовов за каждую секунду.


вот мое решение использовать библиотеку 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));
  }

другие решения не соответствовали моим вкусам. Исследуя дальше, я нашел обещать-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 каждые 2000ms максимум. Другими словами, самый первый запрос не будет ждать 2000мс.