limit-rate: Lua модуль для ограничения скорости запросов для nginx-module-lua, использующий метод "токенов в ведре"
Установка
Если вы еще не настроили подписку на репозиторий RPM, зарегистрируйтесь. После этого вы можете продолжить с следующими шагами.
CentOS/RHEL 7 или Amazon Linux 2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-limit-rate
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-limit-rate
Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.
Этот документ описывает lua-resty-limit-rate v0.1, выпущенный 25 октября 2018 года.
http {
lua_shared_dict my_limit_rate_store 100m;
lua_shared_dict my_locks 100k;
server {
location / {
access_by_lua_block {
local limit_rate = require "resty.limit.rate"
local lim, err = limit_rate.new("my_limit_rate_store", 500, 10, 3, 200, {
lock_enable = true, -- использовать lua-resty-lock
locks_shdict_name = "my_locks",
})
if not lim then
ngx.log(ngx.ERR,
"не удалось создать объект resty.limit.rate: ", err)
return ngx.exit(500)
end
-- следующий вызов должен выполняться для каждого запроса.
-- здесь мы используем удаленный (IP) адрес в качестве ключа ограничения
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
-- local delay, err = lim:take(key, 1, true)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "не удалось взять токен: ", err)
return ngx.exit(500)
end
if delay >= 0.001 then
-- 2-е возвращаемое значение содержит текущее количество доступных токенов
-- запросов для указанного ключа
local avail = err
ngx.sleep(delay)
end
}
# обработчик контента здесь. если это content_by_lua, то вы можете
# объединить код Lua выше в access_by_lua в ваш обработчик content_by_lua
# чтобы сэкономить немного времени ЦП.
}
location /take_available {
access_by_lua_block {
local limit_rate = require "resty.limit.rate"
-- глобально 20r/s 6000r/5m
local lim_global = limit_rate.new("my_limit_rate_store", 100, 6000, 2, nil, {
lock_enable = true,
locks_shdict_name = "my_locks",
})
if not lim_global then
return ngx.exit(500)
end
-- индивидуально 2r/s 600r/5m
local lim_single = limit_rate.new("my_limit_rate_store", 500, 600, 1, nil, {
locks_shdict_name = "my_locks",
})
if not lim_single then
return ngx.exit(500)
end
local t0, err = lim_global:take_available("__global__", 1)
if not t0 then
ngx.log(ngx.ERR, "не удалось взять глобальный: ", err)
return ngx.exit(500)
end
-- здесь мы используем userid в качестве ключа ограничения
local key = ngx.var.arg_userid or "__single__"
local t1, err = lim_single:take_available(key, 1)
if not t1 then
ngx.log(ngx.ERR, "не удалось взять индивидуальный: ", err)
return ngx.exit(500)
end
if t0 == 1 then
return -- глобальное ведро не голодное
else
if t1 == 1 then
return -- индивидуальное ведро не голодное
else
return ngx.exit(503)
end
end
}
}
}
}
Описание
Этот модуль предоставляет API, чтобы помочь пользователям OpenResty/ngx_lua ограничивать скорость запросов, используя метод "токенов в ведре".
Если вы хотите использовать несколько различных экземпляров этого класса одновременно или использовать один экземпляр этого класса с экземплярами других классов (таких как resty.limit.conn), то вы должны использовать модуль resty.limit.traffic для их объединения.
Основное различие между этим модулем и resty.limit.req:
- resty.limit.req ограничивает скорость запросов, используя метод "протекающего ведра", этот модуль использует метод "токенов в ведре".
Основное различие между этим модулем и resty.limit.count:
- resty.limit.count предлагает простую ментальную модель, которая ограничивает скорость запросов фиксированным количеством запросов в заданном временном окне, но иногда может пропустить в два раза больше разрешенных запросов в минуту. Например, если наш лимит скорости составляет 10 запросов в минуту, и пользователь сделал 10 запросов в 10:00:59, он может сделать еще 10 запросов в 10:01:00, потому что новый счетчик начинается в начале каждой минуты. В этом случае этот модуль способен контролировать более точно и плавно.
Методы
new
синтаксис: obj, err = class.new(shdict_name, interval, capacity, quantum?, max_wait?, opts?)
Создает объект этого класса. Значение class возвращается вызовом require "resty.limit.rate".
Метод возвращает новое ведро токенов, которое заполняется со скоростью quantum токенов каждые interval, до заданной максимальной capacity. Ведро изначально полное.
Этот метод принимает следующие аргументы и необязательную таблицу опций opts:
-
shdict_name— это имя зоны shm lua_shared_dict.Рекомендуется использовать отдельные зоны shm для различных типов ограничителей.
-
interval— это время, проходящее между добавлением токенов, в миллисекундах. -
capacity— максимальное количество токенов, которое может храниться в ведре. -
quantum— количество токенов, добавляемых в ведро за один интервал, этот аргумент является необязательным, по умолчанию1. -
max_wait— максимальное время, которое мы будем ждать, чтобы добавить достаточное количество токенов, в миллисекундах, этот аргумент является необязательным, по умолчаниюnil, что означает бесконечность.
Таблица опций принимает следующие параметры:
-
lock_enableКогда включено, обновление состояния shdict между несколькими процессами-рабочими nginx является атомарным; в противном случае будет иметь место (небольшое) окно гонки между поведением "чтение-затем-запись", по умолчаниюfalse. См. lua-resty-lock для получения дополнительных сведений. -
locks_shdict_nameУказывает имя общего словаря (созданного с помощью lua_shared_dict) для блокировки, по умолчаниюlocks.
В случае ошибки этот метод возвращает nil и строку, описывающую ошибку (например, неправильное имя lua_shared_dict).
incoming
синтаксис: delay, err = obj:take(key, commit)
Запускает новое событие входящего запроса и вычисляет необходимую задержку (если такая имеется) для текущего запроса по указанному ключу или решает, следует ли немедленно отклонить его.
Похоже на метод take, но этот метод берет только один токен из ведра за раз.
Этот метод принимает следующие аргументы:
-
key— это ключ, указанный пользователем для ограничения скорости.Обратите внимание, что этот модуль не добавляет префиксы или суффиксы к ключу пользователя, поэтому ответственность за обеспечение уникальности ключа в зоне shm
lua_shared_dictлежит на пользователе. -
commit— это логическое значение. Если установлено вtrue, объект фактически запишет событие в shm-зону, поддерживающую текущий объект; в противном случае это будет просто "пробный запуск" (что является значением по умолчанию).
set_max_wait
синтаксис: obj:set_max_wait(max_wait?)
Перезаписывает порог max_wait, как указано в методе new.
take
синтаксис: delay, err = obj:take(key, count, commit)
Метод берет count токенов из ведра без блокировки.
Этот метод принимает следующие аргументы:
-
key— это ключ, указанный пользователем для ограничения скорости.Обратите внимание, что этот модуль не добавляет префиксы или суффиксы к ключу пользователя, поэтому ответственность за обеспечение уникальности ключа в зоне shm
lua_shared_dictлежит на пользователе. -
count— количество токенов для удаления. -
commit— это логическое значение. Если установлено вtrue, объект фактически запишет событие в shm-зону, поддерживающую текущий объект; в противном случае это будет просто "пробный запуск" (что является значением по умолчанию).
Возвращаемые значения зависят от следующих случаев:
-
Если значение
max_wait, указанное в методе new или set_max_wait, метод будет брать токены из ведра только в том случае, если время ожидания токенов не превышаетmax_wait, и возвращает время, которое вызывающий должен ждать, пока токены фактически не станут доступны, в противном случае возвращаетnilи строку ошибки"rejected". -
Если значение
max_waitравно nil, возвращает время, которое вызывающий должен ждать, пока токены фактически не станут доступны.
Кроме того, этот метод также возвращает второе возвращаемое значение, указывающее количество текущих доступных токенов на данный момент.
Если произошла ошибка (например, сбой при доступе к зоне shm lua_shared_dict, поддерживающей текущий объект), то этот метод возвращает nil и строку, описывающую ошибку.
Этот метод никогда не спит сам по себе. Он просто возвращает задержку, если это необходимо, и требует от вызывающего позже вызвать метод ngx.sleep для ожидания.
take_available
синтаксис: count, err = obj:take_available(key, count)
Метод берет до count немедленно доступных токенов из ведра. Он возвращает количество удаленных токенов или ноль, если доступных токенов нет. Он не блокирует.
Этот метод принимает следующие аргументы:
-
key— это ключ, указанный пользователем для ограничения скорости.Обратите внимание, что этот модуль не добавляет префиксы или суффиксы к ключу пользователя, поэтому ответственность за обеспечение уникальности ключа в зоне shm
lua_shared_dictлежит на пользователе. -
count— количество токенов для удаления.
Если произошла ошибка (например, сбой при доступе к зоне shm lua_shared_dict, поддерживающей текущий объект), то этот метод возвращает nil и строку, описывающую ошибку.
uncommit
синтаксис: ok, err = obj:uncommit(key)
Этот метод пытается отменить фиксацию вызова incoming. Это просто приближение и должно использоваться с осторожностью. Этот метод в основном предназначен для использования в Lua модуле resty.limit.traffic при комбинировании нескольких ограничителей одновременно.
Гранулярность ограничения
Ограничение работает на уровне отдельного экземпляра сервера NGINX (включая все его рабочие процессы). Благодаря механизму shm мы можем дешево делиться состоянием между всеми рабочими процессами в одном экземпляре сервера NGINX.
См. также
- модуль resty.limit.req
- модуль resty.limit.conn
- модуль resty.limit.count
- модуль resty.limit.traffic
- библиотека lua-resty-limit-traffic
- модуль ngx_lua: https://github.com/openresty/lua-nginx-module
- OpenResty: https://openresty.org/
GitHub
Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-limit-rate.