Пятница, 3 Апрель 2009
ngx_http_limit_var_module.c
Модуль ngx_http_limit_var_module (патч на для nginx >= 0.7.52) позволяет ограничить число запросов для заданного ключа (ключем может быть uri и кука, может быть ip или любая строка состоящая как из простых значений, так и из переменных). Основное отличие от ngx_http_limit_req_module заключается в предоставлении информации о необходимости ограничений через механизм переменных, а это позволяет сократить количество выполнений тяжелой части запроса при обработке клиента (или включить кеширование), и в не совсем честном алгоритме.
Пример конфигурации
http {
limit_var $remote_addr zone=one:500000 rate=10r/s;
...
server {
...
location /search/ {
if ($limit_var_one) {
proxy_cache cache_zone;
}
}
Описание директив
syntax: limit_var $переменная zone=название:количество_состояний rate=скорость
default: нет
context: http
Директива описывает зону в которой будут храниться состояния и максимальная скорость для одного состояния. Так же будет создана переменная вида $limit_var_название, в которой будет текущая скорость в r/s при условии что она выше чем ограничения для этого имени.
Скорость задаётся в запросах в секунду.
Описание алгоритмов
В алгоритме хранения могут быть коллизии, когда для нескольких разных ключей используется одно и тоже значение. Их количество зависит только от количества хранимых состояний — чем оно больше, тем их меньше.
Комментарии
Ты, воистину, велик!
Модуль — это очень здорово! Патчи это тоже хорошо, но модули лучше ;)
У меня все мои модули очень сильно связаны, и мне проще выкладывать diff до оригинальной версии :)
Да и править исходный код, оригинальный, местами, я себе разрешаю.
Кирилл, а ограничение реально посекундное? Потому как оригинальный limit_req_module работает не так, как заявлено
А еще вопрос — память увеличивается динамически или сразу выдается по количеству состояний? И когда их число уменьшается, память освобождается или это на совести аллокатора? Интересно кстати как в этом случае тот патч с openbsd аллокатором себя поведет…есть повод потестировать :)
Я так понимаю что память общая для всех воркеров, как ты и любишь делать ;)
И $limit_var_one можно использовать на любых уровнях вложенности?
Можно ли на одном уровне использовать несколько limit_var? Например один по $remote_addr (кстати, наверное лучше для этономии памяти использовать $binary_remote_addr) а другой по $server_name (небольшая защита от перегрузок для бекенда)
А есть идеи как это прикрутить, чтобы работало с двойными условиями?
Например, чтобы ограничить количество коннектов для всех, кроме группы VIP персон (определяемых например по geo модулю), то сейчас можно так организовать:
location / {
set $access 1;
if ($limit_var_one) {
set $access 0;
}
if ($user_vip) {
set $access 0;
}
if ($access) {
proxy_pass http://127.0.0.1:8080/;
include proxy.conf;
}
}
Блин, как же не хватает что-то подобного:
if (!$limit_var_one || $user_vip) {
proxy_pass http://127.0.0.1:8080/;
include proxy.conf;
}
Не совсем так. Сейчас ограничение сделано в виде счатчика. Т.е. если ты уставаливаешь не больше 10 запросов в секунду оно будет пропускать не больше 10 запросов в секунду, сбрасывая за каждую секунду простоя 1 запрос.
т.е. у нас 10 r/s ограничение, и мы делаем за секунду 20 запросов. Это означает что первые 9 штук пройду, а остальные 11 будут привышением. Если мы на следующей секунде счетчик плохих запросов уменьшится на 1 и станет равным 10. На следующий 9. На следующий 8 и теперь мы сможем сделать еще один запрос.
Причина такого алгоритма очень проста — оптимизация по памяти.
Память выделяется одним куском. И не изменяется всю жизнь nginx.
Да, но только после определения.
Можно хоть 16 переменных сделать, главное что бы памяти хватило. Касательно remoteaddr — модулю всеравно. Он всеравно будет считать 32 битных хеш, т.е. ограничение по памяти зависит от колличество состояний а не размера переменной или длини строки с ними ;)
Надо понимать что эти структуры данных кушают очень мало памяти, но имеют коллизии, и коллизия будет считаться как счетчик на двоих. Я постораюсь в ближайшее время выложжить тесты относительно колличетсва состояний, необходимой памяти и процента коллизий, но мне нужно время.
операторы || и && мне предется, все-таки делать в if’ах и я от этого не денусь некуда, следите за патчами :)
То есть если я правильно понимаю, то сделав эти 20 запросов за одну секунду, юзер сможет снова вернуться только через 11 секунд. Хотя за эти 10 секунд ему позволительно сделать 11*10 запросов. То есть поведение немного настораживающее…
Может ввести дополнительную настройку, позволяющую не учитывать все, что сверху, то есть сбрасывать сразу же после этой секунды. Тогда появится необходимый эффект, что именно не больше 10 запросов в секунду. все что сверху — долой, но зато на следующую секунду он может опять сделать до 10. Как-то так:
limit_var $remote_addr zone=one:500000 rate=10r/s zero=1;
И память кстати будет больше экономиться
Форма комментирования для «ngx_http_limit_var_module.c»
пытаюсь наложить патч ngx_http_limit_var_module-0.1.patch после патчей nginx-catap-0.7.47.352.084bd34.diff — ошибку пишет. Можно выложить на сайте коммулятивный пачт для nginx 0.7.47 ?
Сделал патч для v0.7.47.352.084bd34.
В будущем я буду выкладывать два патча, для последней версии оригинального и последней версии своего патча, спасибо.
Угу, это я понимаю, и постораюсь обновить патч сегодня-завтра (добавить опцию сколько запросов списывать за секунду).
На самом деле такой алгоритм очень хорошо защитит от ab и подобных штук ;)
Обновил описания алгоритма — остались вопросы?
зачем считать 32-битный хеш от $remote_addr, если можно использовать $binary_remote_addr, эта переменная равна адресу клиента в бинарном виде, длина её значения всегда 4 байта;
Все очень просто. $remote_addr это частный и не интереснный источник сессии клиента. Куда интересней, например, кука.