# теория · шаг 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
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 - это когда охранник пропускает всех, кто поместился в загон, разом, а
потом закрывает створку на соответствующее время. Пропускная способность та же,
ожидание другое.
Механизм: три режима одной директивы
Правило
limit_req zone=one burst=20 nodelay;
- без
burst- когда не нужен запас на краткий всплеск: запросы сверх темпа нужно отклонять. Это не ограничение одновременного выполнения; burstбезnodelay- когда клиенту лучше подождать, чем получить ошибку: фоновые интеграции, выгрузки по расписанию;burstсnodelay- для живых людей: браузер, открывающий страницу с десятком запросов, не должен ждать секунды на ровном месте.
Для интерфейса почти всегда нужен третий вариант.
Разбор: 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 - середина
limit_req zone=one 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 - сколько соединений клиент держит
прямо сейчас:
limit_conn_zone $binary_remote_addr zone=conn:10m;
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
2 200
3 503
[error] limiting connections by zone "conn"
На быстрых ответах эта директива почти не срабатывает: соединение живёт
миллисекунды. Смысл появляется там, где ответ долгий - скачивания, выгрузки,
потоковые ответы. Пара limit_conn плюс limit_rate из одиннадцатого раздела -
типовая защита канала.
В HTTP/2 и HTTP/3 «соединение» - это запрос
Слово «соединение» здесь означает не транспортное соединение, а обрабатываемый запрос: в HTTP/2 и HTTP/3 каждый одновременный запрос считается отдельным соединением. Браузер мультиплексирует десятки запросов в одном соединении - и все они считаются поштучно.
Замер на 1.31.6, limit_conn conn 2, пять запросов в ОДНОМ соединении HTTP/2:
2 200
3 503
Ровно тот же результат, что у пяти ОТДЕЛЬНЫХ соединений HTTP/1.1. То есть лимит
не стал мягче от того, что клиент пришёл по HTTP/2, - и настраивать его надо
исходя из числа одновременных ЗАПРОСОВ, а не из числа клиентов. Браузер
открывает одно соединение, но может держать в нём шесть-восемь параллельных
загрузок, и limit_conn 2 для страницы с картинками окажется слишком жёстким.
Что ломается без этого
Лимит на статику. limit_req на location / вместе с картинками и css
означает, что открытие страницы с сорока файлами съедает лимит одним человеком.
Ограничивают то, что дорого: логин, поиск, отправку форм, тяжёлые отчёты.
Один лимит на всё. Зона одна, а маршруты разные: странице каталога уместен
burst=20 nodelay, а /api/login - жёсткий rate=1r/s без burst. Зон делают
несколько, по смыслу, и включают в нужных местах.
Лимит применяется на своём уровне. Директива limit_req наследуется по
обычным правилам, но, как и allow, замещается целиком: своя строка в location
отменяет унаследованные.
Зачем это в работе
Практический рецепт для типового сайта:
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;
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 разрешает временное превышение темпа, а nodelay определяет, должны ли разрешённые запросы ждать. Сравни и число принятых запросов, и их задержку. Для одновременных обрабатываемых запросов нужен отдельный limit_conn: ограничение частоты не заменяет ограничение одновременности.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий