Обмеження швидкості обробки запитів в nginx

  • 11.07.17 7:57 •
  • olemskoi •
  • # 329876 •
  • Хабрахабр •
  • Переклад •
  • 11 •
  • 4500

- такий же як Forbes, тільки краще.

Обмеження швидкості обробки запитів в nginx

Фотографія користувача Wonderlane, Flickr

NGINX чудовий! Ось тільки його документація щодо обмеження швидкості обробки запитів здалася мені, як би це сказати, наскільки обмеженою. Тому я вирішив написати цей посібник щодо обмеження швидкості обробки запитів (rate-liming) і шейпінгу трафіку (traffic shaping) в NGINX.


  • описати директиви NGINX,
  • розібратися з accept / reject-логікою NGINX,
  • візуалізувати обробку сплесків трафіку на різних настройках.

На додаток я створив GitHub-репозиторій і Docker-образ. з якими можна поекспериментувати і відтворити наведені в цій статті тести. Завжди легше вчитися на практиці.

Директиви NGINX щодо обмеження швидкості обробки запитів

У цій статті ми будемо говорити про ngx_http_limit_req_module. в якому реалізовані директиви limit_req_zone. limit_req. limit_req_status і limit_req_level. Вони дозволяють управляти значенням коду стану HTTP-запиту для відхилених (rejected) запитів, а також логування цих відмов.

Найчастіше плутаються саме в логіці відхилення запиту.

Спочатку потрібно розібратися з директивою limit_req. якої потрібно параметр zone. У нього також є необов'язкові параметри burst і nodelay.

Тут використовуються такі концепції:

burst - необов'язковий параметр. Будучи встановленим, він визначає кількість запитів, яке може бути оброблено понад установлений базового обмеження швидкості. Важливо розуміти, що burst - це абсолютна величина кількості запитів, а не швидкість.


  • nodelay - також необов'язковий параметр, який використовується спільно з burst. Нижче ми розберемося, навіщо він потрібен.

  • Яким чином NGINX приймає рішення про прийняття або відхилення запиту?

    Під час налаштування зони задається її швидкість. Наприклад, при 300r / m буде прийнято 300 запитів в хвилину, а при 5r / s - 5 запитів в секунду.


    • limit_req_zone $ request_uri zone = zone1: 10m rate = 300r / m;
    • limit_req_zone $ request_uri zone = zone2: 10m rate = 5 / s;

    Важливо розуміти, що ці дві зони мають однакові ліміти. За допомогою параметра rate NGINX розраховує частоту і, відповідно, інтервал, після якого можна прийняти новий запит. В даному випадку NGINX буде використовувати алгоритм під назвою «діряве відро» (leaky bucket).

    Для NGINX 300r / m і 5r / s однакові: він буде пропускати один запит кожні 0,2 с. В даному випадку NGINX кожні 0,2 секунди буде встановлювати прапор, який дозволяє прийом запиту. Коли приходить відповідний для цієї зони запит, NGINX знімає прапор і обробляє запит. Якщо приходить черговий запит, а таймер, який вважає час між пакетами, ще не спрацював, запит буде відхилено з кодом стану 503. Якщо час минув, а прапор уже встановлений в яке дозволяє прийом значення, ніяких дій виконано не буде.

    Чи потрібні обмеження швидкості обробки запитів і шейпінг трафіку?

    Поговоримо про параметр burst. Уявіть, що прапор, про який ми говорили вище, може приймати значення більше одиниці. У цьому випадку він буде відображати максимальну кількість запитів, які NGINX повинен пропустити в рамках однієї пачки (burst).

    Тепер це вже не «діряве відро», «маркерная кошик» (token bucket). Параметр rate визначає часовий інтервал між запитами, але ми маємо справу не з токеном типу true / false, а з лічильником від 0 до 1 + burst. Лічильник збільшується кожен раз, коли проходить розрахований інтервал часу (спрацьовує таймер), досягаючи максимального значення в b + 1. Нагадаю ще раз: burst - це кількість запитів, а не швидкість їх пропускання.

    Коли приходить новий запит, NGINX перевіряє доступність токена (лічильник> 0). Якщо токен недоступний, запит відхиляється. В іншому випадку запит приймається і буде оброблений, а токен сеанс відтворення (лічильник зменшується на один).

    Добре, якщо є невитрачені burst-токени, NGINX прийме запит. Але коли він його обробить?

    Ми встановили ліміт в 5r / s, при цьому NGINX прийме запити понад норму, якщо є доступні burst-токени, але відкладе їх обробку таким чином, щоб витримати встановлену швидкість. Тобто ці burst-запити будуть оброблені з деякою затримкою або завершаться по таймаут.

    Іншими словами, NGINX не перевищить встановлений для зони ліміт, а поставить додаткові запити в чергу і обробить їх з деякою затримкою.

    Наведемо простий приклад: скажімо, у нас встановлений ліміт 1r / s і burst дорівнює 3. Що буде, якщо NGINX отримає відразу 5 запитів?


    • Перший буде прийнятий і оброблений.
    • Оскільки дозволено не більше 1 + 3, один запит буде відразу відхилений з кодом стану 503.
    • Три залишилися будуть оброблені один за іншим, але не миттєво. NGINX пропустить їх зі швидкістю 1r / s. залишаючись в рамках встановленого ліміту, а також за умови, що не будуть чинити нові запити, які також використовують квоту. Коли черга спорожніє, лічильник пачки (burst counter) знову почне збільшуватися (маркерная кошик почне наповнюватися).

    У разі використання NGINX в якості проксі-сервера розташовані за ним сервіси будуть отримувати запити зі швидкістю 1r / s і нічого не дізнаються про сплесках трафіку, згладжених проксі-сервером.

    Отже, ми тільки що налаштували шейпінг трафіку, застосувавши затримки для управління сплесками запитів і вирівнювання потоку даних.

    nodelay говорить NGINX, що він повинен приймати пакети в рамках вікна, визначеного значенням burst. і відразу їх обробляти (так само як і звичайні запити).

    В результаті сплески трафіку все ж будуть досягати сервісів, розташованих за NGINX, але ці сплески будуть обмежені значенням burst.

    Візуалізація обмежень швидкості обробки запитів

    Оскільки я вірю, що практика дуже допомагає в запам'ятовуванні чого б то не було, я зробив невеличкий Docker-образ з NGINX на борту. Там налаштовані ресурси, для яких реалізовані різні варіанти обмеження швидкості обробки запитів: з базовим обмеженням, з обмеженням швидкості, що використовують burst. а також з burst і nodelay. Давайте подивимося, як вони працюють.

    Тут використовується досить проста конфігурація NGINX (вона також є в Docker-образі, посилання на який можна знайти в кінці статті):

    Тестова конфігурація NGINX з різними варіантами обмеження швидкості обробки запитів

    У всіх тестах, використовуючи цю конфігурацію, ми відправляємо одночасно по 10 паралельних запитів.

    Давайте з'ясуємо ось що:


    • скільки запитів буде відхилено через обмеження швидкості?
    • яка швидкість обробки отриманих запитів?

    Робимо 10 паралельних запитів до ресурсу з обмеженням швидкості обробки запитів

    10 одночасних запитів до ресурсу з обмеженням швидкості обробки запитів

    В нашій конфігурації дозволено 30 запитів в хвилину. Але в даному випадку 9 з 10 претендента будуть дискваліфіковані. Якщо ви уважно читали попередні розділи, така поведінка NGINX не стане для вас несподіванкою: 30r / m означає, що проходити буде тільки один запит в 2 секунди. У нашому прикладі 10 запитів приходять одночасно, один пропускається, а решта дев'ять відхиляються, оскільки NGINX їх видно до того, як спрацює таймер, що дозволяє наступний запит.

    Я переживу невеликі сплески запитів до клієнтів / кінцевих точок

    Добре! Тоді додамо аргумент burst = 5. який дозволить NGINX пропускати невеликі сплески запитів до даної кінцевій точці зони з обмеженням швидкості обробки запитів:

    10 одночасних запитів до ресурсу з аргументом burst = 5

    Що тут сталося? Як і слід було очікувати, з аргументом burst було прийнято 5 додаткових запитів, і ми поліпшили ставлення отриманих запитів до загального їх числа з 1/10 до 6/10 (інші були відхилені). Тут добре видно, як NGINX оновлює токен і обробляє прийняті запити - вихідна швидкість обмежена 30r / m. що дорівнює одному запиту кожні 2 секунди.

    Відповідь на перший запит повертається через 0,2 секунди. Таймер спрацьовує через 2 секунди, один з очікують запитів обробляється, і клієнтові приходить відповідь. Загальний час, витрачений на дорогу туди і назад, склало 2,02 секунди. Ще через 2 секунди знову спрацьовує таймер, даючи можливість обробити черговий запит, який повертається із загальним часом в дорозі, рівним 4,02 секунди. І так далі і тому подібне…

    Таким чином, аргумент burst дозволяє перетворити систему обмеження швидкості обробки запитів NGINX з простого порогового фільтра в шейпер трафіку.

    Мій сервер витримає додаткове навантаження, але я б хотів використовувати обмеження швидкості обробки запитів для запобігання його перевантаження

    У цьому випадку може виявитися корисним аргумент nodelay. Давайте пошлемо ті ж самі 10 запитів кінцевій точці з налаштуванням burst = 5 nodelay:

    10 одночасних запитів до ресурсу з аргументом burst = 5 nodelay

    Як і очікувалося з burst = 5. у нас залишиться таке ж співвідношення 200-х і 503-х кодів стану. Але вихідна швидкість тепер не обмежена одним запитом кожні 2 секунди. Поки доступні burst-токени, вхідні запити будуть прийматися і тут же оброблятися. Швидкість спрацьовування таймера все так само важлива з точки зору поповнення кількості burst-токенів, але на прийняті запити затримка тепер не поширюється.

    Підведемо підсумки

    Спробуємо візуалізувати те, як NGINX приймає вхідні запити і обробляє їх на основі параметрів rate. burst і nodelay.

    Щоб не ускладнювати, відобразимо кількість вхідних запитів (які потім відхиляються або приймаються і обробляються) на обумовленою в настройках зони часовій шкалі, розбитою на рівні значенням спрацювання таймера відрізки. Абсолютне значення тимчасового інтервалу не суттєво. Важливо кількість запитів, яке NGINX може обробити на кожному кроці.

    Ось трафік, який ми будемо проганяти через різні настройки обмеження швидкості обробки запитів:


    Обмеження швидкості обробки запитів в nginx

    Вхідні запити і обмеження швидкості обробки запитів, заданий для зони


    Обмеження швидкості обробки запитів в nginx

    Прийняті і відхилені запити (настройка burst не задана)

    Без burst (тобто при burst = 0) NGINX виконує функцію обмежувача швидкості. Запити або відразу обробляються, або відразу відхиляються.

    Якщо ж ми хочемо вирішити невеликі сплески трафіку, наприклад, щоб довантажити потужності в рамках встановленого ліміту, тоді можна додати аргумент burst. який має на увазі затримку при обробці запитів, прийнятих в рамках доступних burst-токенів:


    Обмеження швидкості обробки запитів в nginx

    Прийняті, прийняті з затримкою і відхилені запити (з використанням burst)

    Ми бачимо, що загальне число відхилених запитів зменшилася. Було відхилено тільки ті перевищують встановлену швидкість запити, які прийшли в моменти, коли не було доступних burst-токенів. З такими настройками NGINX виконує повноцінний шейпінг трафіку.

    Нарешті, NGINX можна використовувати для управління трафіком шляхом обмеження розміру пачки запитів (burst), але при цьому сплески запитів частково будуть доходити до їх обробників (вищестоящих або локальних), що в кінцевому рахунку призведе до менш стабільною вихідної швидкості, але поліпшить мережеву затримку ( якщо ви, звичайно, можете обробити ці додаткові запити):


    Обмеження швидкості обробки запитів в nginx

    Прийняті, оброблені і відхилені запити (burst використовується з nodelay)

    Пограйте з обмеженням швидкості обробки запитів

    Тепер, щоб краще закріпити розуміння викладених концепцій, ви можете вивчити код, скопіювати репозиторій і поекспериментувати з підготовленим Docker-образом:

    Не бачу, чому б для стислості в перекладі терміна rate-limiting не відкинула останні два слова. Думаю, що всім зрозуміло, про яке «обмеження швидкості» в контексті nginx йде мова. Ніякого іншого тлумачення і можливої ​​двозначності бути не повинно.

    Для мене швидкість - це limit_rate (воно і раніше з'явилося в nginx). В оригіналі, виходить, теж кострубато написано ... підміна понять)) що ж ви все-таки обмежуєте, по вашій статті _не понятно_. Більш того, ви пишете про шейпінг трафіку, а про нього нічого немає)))

    Схожі статті