1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6

# теория · шаг 2 из 6

burst, nodelay и limit_conn

Коротко

Голый limit_req отвергает всё лишнее мгновенно. burst добавляет к ведру очередь, nodelay пропускает очередь сразу, delay=N - середина.

  • Замер при rate=1r/s и десяти одновременных: без burst - 1 успех, с burst=3 - 4, и трое из них ждут 1, 2 и 3 секунды.
  • С nodelay те же четверо проходят мгновенно, а лимит всё равно считается.
  • delay=2 пропускает сразу двоих сверх нормы, остальных ставит в очередь.
  • limit_conn считает не частоту, а одновременность: пять скачиваний при limit_conn 2 дают 2 успеха и 3 отказа.

Если различаешь burst, nodelay и delay= - листай до «Теперь сам».

Сначала ответь сам

rate=1r/s, burst=3, десять одновременных запросов. Сколько пройдёт и как быстро?

$  команда
for i in $(seq 10); do curl -s -o /dev/null -w '%{http_code} %{time_total}\n' http://localhost/api/x & done; wait | sort -k2 -n
↳  выводfor i in $(seq 10); do curl ... ; done (burst=3, коды и время в секундах)
200 0.006
200 0.995
200 1.985
200 2.983
503 0.001
503 0.001
503 0.001
503 0.001
503 0.001
503 0.003

Четыре успеха: один сразу и трое из очереди - 0.99, 1.98, 2.98 секунды. Три запроса ждали примерно одну, две и три секунды. Ведро углубили, но течёт оно с прежней скоростью, и очередь рассасывается по одному в секунду.

На что это похоже

Тот же турникет, но перед ним поставили загон на трёх человек. Толпа из десяти: один проходит сразу, трое встают в загон и проходят по очереди, шестерым не хватило места - разворачиваются.

nodelay - это когда охранник пропускает всех, кто поместился в загон, разом, а потом закрывает створку на соответствующее время. Пропускная способность та же, ожидание другое.

Механизм: три режима одной директивы

без burst один проходит, девять сразу получают 503 burst=3 один сразу, трое ждут 1, 2 и 3 секунды клиент видит медленный, но успешный ответ burst=3 nodelay четверо проходят мгновенно но следующий запрос этого клиента упрётся раньше пропущено везде одинаково: burst меняет ожидание, а не пропускную способность
Число прошедших одинаково во всех трёх режимах. Разница в том, ждёт клиент или получает отказ.

Правило

/etc/nginx/conf.d/shop.local.confhttp server location /api/
limit_req zone=one burst=20 nodelay;
  • без burst - только для того, что действительно должно идти по одному: отправка кода подтверждения, дорогая выгрузка;
  • burst без nodelay - когда клиенту лучше подождать, чем получить ошибку: фоновые интеграции, выгрузки по расписанию;
  • burst с nodelay - для живых людей: браузер, открывающий страницу с десятком запросов, не должен ждать секунды на ровном месте.

Для интерфейса почти всегда нужен третий вариант.

Разбор: nodelay не отменяет лимит

↳  выводте же десять одновременных, но burst=3 nodelay
200 0.002
200 0.002
200 0.005
200 0.010
503 0.001
503 0.001
503 0.001
503 0.001
503 0.002
503 0.002

Четыре успеха, как и с обычным burst, но время у всех около нуля - - но без единой секунды ожидания. Пропускная способность не изменилась: ведро всё так же вмещает 1 + burst и течёт со скоростью rate.

Разница в том, когда клиент узнаёт об отказе. С nodelay он получает свои четыре ответа мгновенно и упирается в лимит на пятом; без nodelay он получает все четыре, но последний - через три секунды.

Разбор: delay=N - середина

/etc/nginx/conf.d/shop.local.confhttp server location /api/
limit_req zone=one burst=5 delay=2;
↳  выводвосемь одновременных запросов, burst=5 delay=2
200 0.001
200 0.001
200 0.006
200 0.992
200 1.991
200 2.989
503 0.001
503 0.001

Читается так: первые два сверх нормы пропускаем мгновенно, остальных до burst ставим в очередь, что не поместилось - отвергаем. Это разумное умолчание для публичного API: короткая пачка проходит без задержки, длинная притормаживается, совсем длинная отвергается.

Разбор: limit_conn - про другое

limit_req считает частоту, limit_conn - сколько соединений клиент держит прямо сейчас:

/etc/nginx/nginx.confhttp
limit_conn_zone $binary_remote_addr zone=conn:10m;
/etc/nginx/conf.d/shop.local.confhttp server location /downloads/
limit_conn conn 2;
$  команда
for i in 1 2 3 4 5; do curl -s -o /dev/null -w '%{http_code}\n' http://localhost/downloads/big & done; wait | sort | uniq -c
↳  выводпять одновременных скачиваний при limit_conn 2 (файл отдаётся с limit_rate)
      2 200
      3 503
↳  выводerror.log
[error] limiting connections by zone "conn"

На быстрых ответах эта директива почти не срабатывает: соединение живёт миллисекунды. Смысл появляется там, где ответ долгий - скачивания, выгрузки, потоковые ответы. Пара limit_conn плюс limit_rate из одиннадцатого раздела - типовая защита канала.

Что ломается без этого

Лимит на статику. limit_req на location / вместе с картинками и css означает, что открытие страницы с сорока файлами съедает лимит одним человеком. Ограничивают то, что дорого: логин, поиск, отправку форм, тяжёлые отчёты.

Один лимит на всё. Зона одна, а маршруты разные: странице каталога уместен burst=20 nodelay, а /api/login - жёсткий rate=1r/s без burst. Зон делают несколько, по смыслу, и включают в нужных местах.

Лимит применяется на своём уровне. Директива limit_req наследуется по обычным правилам, но, как и allow, замещается целиком: своя строка в location отменяет унаследованные.

Зачем это в работе

Практический рецепт для типового сайта:

/etc/nginx/nginx.confhttp
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=login:10m   rate=1r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;
/etc/nginx/conf.d/shop.local.confhttp server
limit_req  zone=general burst=50 nodelay;
limit_conn conn 20;

location = /api/login {
    limit_req zone=login burst=3 nodelay;
    limit_req_status 429;
    proxy_pass http://app:8000;
}

Общий щедрый лимит на весь сайт плюс жёсткий на точку входа. И limit_req_status 429 там, где с клиентом разговаривает программа, а не человек.

Теперь сам

Поставили limit_req zone=one burst=100 nodelay; при rate=1r/s - «чтобы никого не отрезать». Через месяц бот перебирает пароли, и лимит его не останавливает. Почему?

Потому что burst=100 разрешает пачку в сто запросов до начала ограничений, а боту этого хватает: он делает сотню попыток мгновенно, потом ждёт и повторяет. Большой burst уместен там, где пачка нормальна (страница с десятками файлов), и вреден на точке входа. На /api/login ставят маленький burst и код 429.

Главное

burst углубляет ведро, но не ускоряет вытекание: замер при rate=1r/s и десяти одновременных даёт 4 успеха и с burst=3, и с burst=3 nodelay - разница только в том, ждут ли трое из них по 1, 2 и 3 секунды. delay=N пропускает сразу N сверх нормы, а остальных ставит в очередь. limit_conn считает не частоту, а одновременность, и осмыслен на долгих ответах: пять скачиваний при limit_conn 2 дают два успеха. Зон делают несколько - щедрую на весь сайт и жёсткую на точку входа.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.