Упростите алгоритм ограничения скорости для сервера Node.js.

Я придумал наивное решение для алгоритма ограничения скорости для сервера Node.js, и я считаю, что есть способ упростить его, но я пока не уверен, как это сделать.

Мы хотим ограничить запросы до 50 в секунду. Таким образом, если приходит новейший запрос, и время между самым новым запросом и запросом, отстоящим на 50 позиций, составляет менее одной секунды, мы должны отклонить новый запрос.

Наивным способом реализовать это было бы иметь простой массив, содержащий 50 меток времени. Каждый раз, когда приходит событие, мы присваиваем ему значение Date.now() / process.hrtime(). Затем мы смотрим значение временной метки 50-й (последней) временной метки в очереди и значение Date.now() нового запроса, и если разница в временных метках превышает 1 секунду, мы принимаем новый запрос и переносим его на «очередь» и удалить самую старую временную метку из очереди. Однако, если разница составляет менее 1 секунды, мы должны отклонить запрос, и мы не перемещаем его в очередь, и мы не удаляем самую старую временную метку.

Вот код, который у меня есть на сервере Express

var mostRecentRequestsTimestamps = [];

app.use(function(req,res,next){

    if(req.baymaxSource && String(req.baymaxSource).toUpperCase() === 'XRE'){

        var now = process.hrtime(); //nanoseconds

        if(mostRecentRequestsTimestamps.length < 50){
            mostRecentRequestsTimestamps.unshift(now);
            next();
        }
        else{
            var lastItem = mostRecentRequestsTimestamps.length -1;
            if(now - mostRecentRequestsTimestamps[lastItem] < 1000){  // 1000 milliseconds = 1 second
                res.status(503).json({error: 'Server overwhelmed by XRE events'});
            }
            else{
                mostRecentRequestsTimestamps.pop();
                mostRecentRequestsTimestamps.unshift(now);
                next();
            }
        }
    }
    else{
        next();
    }

});

Как вы могли видеть, он блокирует события только в том случае, если они исходят из одного конкретного источника, поэтому он не должен ограничивать другие типы запросов. Для этой логики требуется структура данных из 50 временных меток, что по сути ничего не значит, но я хотел бы еще больше упростить это, если это возможно. У кого-нибудь есть идеи? спасибо


person Alexander Mills    schedule 02.02.2016    source источник
comment
Разве ваше решение не требует массива для каждого отдельного пользователя, использующего ваш сервис? Существует много, много статей, написанных о различных стратегиях ограничения скорости, в том числе многие с примерами кода. Вы уже проводили какие-либо собственные исследования по этому поводу? Если да, то что вы нашли и какие у вас есть вопросы к тому, что уже было написано на эту тему? Если нет, то я предлагаю вам начать с чтения о том, что уже разработано в этой области.   -  person jfriend00    schedule 02.02.2016
comment
нет, это просто однопоточный сервер node.js, просто нужен один массив. Я вижу много библиотек и примеров ограничения скорости, которые кажутся либо общими решениями, либо плохо реализованными и т. д.   -  person Alexander Mills    schedule 02.02.2016
comment
Если у вас есть только один массив и у вас есть N разных пользователей, делающих запросы к вашему серверу, то вы будете ограничивать скорость всего населения вместе. Пользователь A может сделать 50 быстрых запросов, а пользователь B может сделать один запрос, и, если пользователю B немного не повезет с его синхронизацией по сравнению с пользователем A, тогда скорость userB будет ограничена, даже если он сделал только один запрос. Это НЕ то, как должно работать ограничение скорости. Я думаю, тебе нужно переосмыслить то, что ты делаешь. Тот факт, что node.js является однопоточным, не имеет ничего общего с этой проблемой. Многие разные пользователи по-прежнему могут делать запросы.   -  person jfriend00    schedule 03.02.2016
comment
Некоторые существующие реализации и алгоритмы: ограничитель скорости node.js, ratelimit.js, токен Алгоритм ведра, Алгоритм дырявого ведра.   -  person jfriend00    schedule 03.02.2016
comment
спасибо, я проверю их   -  person Alexander Mills    schedule 03.02.2016
comment
Один из основных вопросов касается вашей цели. Вы пытаетесь сохранить одного пользователя за чрезмерное использование сервера, чтобы данный пользователь был ограничен в количестве запросов, которые он может выполнять в течение установленного периода времени? Или вы пытаетесь ограничить общее количество запросов, которые могут быть сделаны в течение определенного периода времени, и любой пользователь, который придет, может быть оштрафован, если сервер окажется занятым. Один из них заключается в том, чтобы удержать одного пользователя от плохого поведения и испортить сервис для всех остальных. Другой касается защиты сервера, но не пытается понять справедливость.   -  person jfriend00    schedule 03.02.2016
comment
@ jfriend00 вы абсолютно правы насчет ограничения скорости для пользователя, это имеет большой смысл, и я не думал об этом, но именно поэтому я говорю, что нам не нужно универсальное решение, потому что наш сервер получает запросы только от еще один сервер   -  person Alexander Mills    schedule 03.02.2016
comment
Все существующие решения позволяют обрабатывать все запросы так, как будто они исходят из одного и того же источника, если вы этого хотите. Чем больше я об этом думаю, тем больше я думаю, что ваш вопрос не совсем уместен здесь, в Stack Overflow. По сути, вы просите людей дать совет по дизайну, на который нет конкретного правильного ответа, а не задаете конкретный вопрос с конкретным ответом.   -  person jfriend00    schedule 03.02.2016
comment
Я не собираюсь ссориться с вами, так как вы всегда так полезны на SO :), но я полагаю, что вы более чем можете (а) улучшить мое решение (б) указать мне на существующие решения, которые у вас уже есть, или ( c) предоставить альтернативное решение и кратко рассказать о нем в ответе или (d)?   -  person Alexander Mills    schedule 03.02.2016
comment
Я хочу, чтобы моя команда реализовала это самостоятельно, мы хотим иметь полное знание и полную ответственность за то, как это работает. Я не хочу использовать для этого чужую библиотеку.   -  person Alexander Mills    schedule 03.02.2016
comment
Если вы показали свой код, описали, что с ним работает хорошо, а что вас не устраивает, и задали конкретный вопрос о том, как улучшить какой-то его аспект, это, безусловно, соответствует приведенным здесь рекомендациям. Например, я даже не знаю, что не так с одним массивом из 50 меток времени. Мне кажется чертовски простым. Так что я даже не понимаю, какой вопрос вы задаете.   -  person jfriend00    schedule 03.02.2016
comment
Добавил код, согласен что без кода плохо   -  person Alexander Mills    schedule 03.02.2016


Ответы (1)


Вот самое простое, что я могу сделать:

// oldest request time is at front of the array
var recentRequestTimes = [];
var maxRequests = 50;
var maxRequestsTime = 1000;

app.use(function(req,res,next){
    if(req.baymaxSource && String(req.baymaxSource).toUpperCase() === 'XRE'){
        var old, now = Date.now();
        recentRequestTimes.push(now);
        if (recentRequestTimes.length >= maxRequests) {
            // get the oldest request time and examine it
            old = recentRequestTimes.shift();
            if (now - old <= maxRequestsTime) {
                // old request was not very long ago, too many coming in during that maxRequestsTime
                res.status(503).json({error: 'Exceeded 50 requests per second for XRE events'});
                return;
            }
        }
    }
    next();
});

Это концептуально отличается от вашей реализации двумя способами:

  1. Я использую массив recentRequestTimes в порядке возрастания (просто это стало более логичным для моего программистского мозга)
  2. Я всегда добавляю каждый запрос в массив, даже когда он перегружен. Вы не учитывали запросы, вызвавшие перегрузку, что я считаю неправильным. Это также упрощает код, поскольку вы можете просто добавить текущее время в начале функции в одном месте, а затем просто обработать массив.
person jfriend00    schedule 03.02.2016
comment
Спасибо, это выглядит хорошо, я проверил это, и это работает. Я считаю, что это простое улучшение по сравнению со странными реализациями ограничения скорости, такими как эта npmjs.com/ package/rolling-rate-limiter - person Alexander Mills; 04.02.2016
comment
Я создал свою собственную библиотеку, которая реализует это: npmjs.com/package/curtain, это работа в процессе, но план состоит в том, чтобы она была проще, чем любые другие библиотеки - person Alexander Mills; 04.02.2016