Подтвердить что ты не робот

Запросы 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.

4b9b3361

Ответ 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

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

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

Существует решение с использованием модели 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 мс.