limit-rate: Módulo Lua para limitar a taxa de requisições para nginx-module-lua, usando o método "token bucket"
Instalação
Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Depois, você pode prosseguir com os seguintes passos.
CentOS/RHEL 7 ou 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
Para usar esta biblioteca Lua com o NGINX, certifique-se de que o nginx-module-lua está instalado.
Este documento descreve lua-resty-limit-rate v0.1 lançado em 25 de outubro de 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, -- usar lua-resty-lock
locks_shdict_name = "my_locks",
})
if not lim then
ngx.log(ngx.ERR,
"falha ao instanciar um objeto resty.limit.rate: ", err)
return ngx.exit(500)
end
-- a chamada a seguir deve ser por requisição.
-- aqui usamos o endereço remoto (IP) como a chave de limitação
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, "falha ao pegar token: ", err)
return ngx.exit(500)
end
if delay >= 0.001 then
-- o 2º valor de retorno contém o número atual de tokens disponíveis
-- de requisições para a chave especificada
local avail = err
ngx.sleep(delay)
end
}
# o manipulador de conteúdo vai aqui. se for content_by_lua, então você pode
# mesclar o código Lua acima em access_by_lua no manipulador Lua do seu content_by_lua
# para economizar um pouco de tempo de CPU.
}
location /take_available {
access_by_lua_block {
local limit_rate = require "resty.limit.rate"
-- global 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
-- single 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, "falha ao pegar global: ", err)
return ngx.exit(500)
end
-- aqui usamos o userid como a chave de limitação
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, "falha ao pegar single: ", err)
return ngx.exit(500)
end
if t0 == 1 then
return -- o balde global não está faminto
else
if t1 == 1 then
return -- o balde único não está faminto
else
return ngx.exit(503)
end
end
}
}
}
}
Descrição
Este módulo fornece APIs para ajudar os programadores usuários do OpenResty/ngx_lua a limitar a taxa de requisições usando o método "token bucket".
Se você quiser usar várias instâncias diferentes desta classe ao mesmo tempo ou usar uma instância desta classe com instâncias de outras classes (como resty.limit.conn), então você deve usar o módulo resty.limit.traffic para combiná-los.
A principal diferença entre este módulo e resty.limit.req:
- resty.limit.req limita a taxa de requisições usando o método "leaky bucket", enquanto este módulo usa o método "token bucket".
A principal diferença entre este módulo e resty.limit.count:
- resty.limit.count oferece um modelo mental direto que limita a taxa de requisições por um número fixo de requisições em uma janela de tempo dada, mas pode, às vezes, permitir o dobro do número de requisições permitidas por minuto. Por exemplo, se nosso limite de taxa fosse 10 requisições por minuto e um usuário fizesse 10 requisições às 10:00:59, ele poderia fazer mais 10 requisições às 10:01:00 porque um novo contador começa no início de cada minuto. Nesse caso, este módulo é capaz de controlar de forma mais precisa e suave.
Métodos
new
sintaxe: obj, err = class.new(shdict_name, interval, capacity, quantum?, max_wait?, opts?)
Instancia um objeto desta classe. O valor class é retornado pela chamada require "resty.limit.rate".
O método retorna um novo token bucket que se enche à taxa de quantum tokens a cada interval, até a capacidade máxima capacity dada. O balde está inicialmente cheio.
Este método aceita os seguintes argumentos e uma tabela de opções opts opcional:
-
shdict_nameé o nome da zona shm lua_shared_dict.É uma boa prática usar zonas shm separadas para diferentes tipos de limitadores.
-
intervalé o tempo que passa entre a adição de tokens, em milissegundos. -
capacityé o número máximo de tokens a serem mantidos no balde. -
quantumé o número de tokens a serem adicionados ao balde em um intervalo, este argumento é opcional, padrão1. -
max_waité o tempo máximo que esperaríamos para que tokens suficientes sejam adicionados, em milissegundos, este argumento é opcional, padrãonil, o que significa infinito.
A tabela de opções aceita as seguintes opções:
-
lock_enableQuando habilitado, a atualização do estado do shdict entre múltiplos processos de trabalho do nginx é atômica; caso contrário, haverá uma janela de condição de corrida (pequena) entre o comportamento de "ler-e-depois-escrever", padrãofalse. Veja lua-resty-lock para mais detalhes. -
locks_shdict_nameEspecifica o nome do dicionário compartilhado (criado por lua_shared_dict) para o bloqueio, padrãolocks.
Em caso de falha, este método retorna nil e uma string descrevendo o erro (como um nome de lua_shared_dict inválido).
incoming
sintaxe: delay, err = obj:take(key, commit)
Dispara um novo evento de requisição e calcula o atraso necessário (se houver) para a requisição atual com base na chave especificada ou se o usuário deve rejeitá-la imediatamente.
Semelhante ao método take, mas este método só pega um token do balde por vez.
Este método aceita os seguintes argumentos:
-
keyé a chave especificada pelo usuário para limitar a taxa.Por favor, note que este módulo não prefixa nem sufixa a chave do usuário, portanto, é responsabilidade do usuário garantir que a chave seja única na zona shm
lua_shared_dict. -
commité um valor booleano. Se definido comotrue, o objeto realmente registrará o evento na zona shm que suporta o objeto atual; caso contrário, será apenas uma "execução a seco" (que é o padrão).
set_max_wait
sintaxe: obj:set_max_wait(max_wait?)
Sobrescreve o limite max_wait conforme especificado no método new.
take
sintaxe: delay, err = obj:take(key, count, commit)
O método pega count tokens do balde sem bloquear.
Este método aceita os seguintes argumentos:
-
keyé a chave especificada pelo usuário para limitar a taxa.Por favor, note que este módulo não prefixa nem sufixa a chave do usuário, portanto, é responsabilidade do usuário garantir que a chave seja única na zona shm
lua_shared_dict. -
counté o número de tokens a serem removidos. -
commité um valor booleano. Se definido comotrue, o objeto realmente registrará o evento na zona shm que suporta o objeto atual; caso contrário, será apenas uma "execução a seco" (que é o padrão).
Os valores de retorno dependem dos seguintes casos:
-
Se o valor
max_waitespecificado no método new ou set_max_wait, o método só pegará tokens do balde se o tempo de espera pelos tokens não for maior quemax_wait, e retorna o tempo que o chamador deve esperar até que os tokens estejam realmente disponíveis, caso contrário, retornanile a string de erro"rejected". -
Se o valor
max_waitfor nil, ele retorna o tempo que o chamador deve esperar até que os tokens estejam realmente disponíveis.
Além disso, este método também retorna um segundo valor de retorno indicando o número de tokens disponíveis neste ponto.
Se ocorrer um erro (como falhas ao acessar a zona shm lua_shared_dict que suporta o objeto atual), então este método retorna nil e uma string descrevendo o erro.
Este método nunca dorme por si só. Ele simplesmente retorna um atraso, se necessário, e requer que o chamador invoque posteriormente o método ngx.sleep para dormir.
take_available
sintaxe: count, err = obj:take_available(key, count)
O método pega até count tokens imediatamente disponíveis do balde. Ele retorna o número de tokens removidos, ou zero se não houver tokens disponíveis. Não bloqueia.
Este método aceita os seguintes argumentos:
-
keyé a chave especificada pelo usuário para limitar a taxa.Por favor, note que este módulo não prefixa nem sufixa a chave do usuário, portanto, é responsabilidade do usuário garantir que a chave seja única na zona shm
lua_shared_dict. -
counté o número de tokens a serem removidos.
Se ocorrer um erro (como falhas ao acessar a zona shm lua_shared_dict que suporta o objeto atual), então este método retorna nil e uma string descrevendo o erro.
uncommit
sintaxe: ok, err = obj:uncommit(key)
Isso tenta desfazer o commit da chamada incoming. Isso é simplesmente uma aproximação e deve ser usado com cuidado. Este método é principalmente para ser usado no módulo Lua resty.limit.traffic ao combinar múltiplos limitadores ao mesmo tempo.
Granularidade de Limitação
A limitação funciona na granularidade de uma instância individual do servidor NGINX (incluindo todos os seus processos de trabalho). Graças ao mecanismo shm; podemos compartilhar o estado de forma econômica entre todos os trabalhadores em uma única instância do servidor NGINX.
Veja Também
- módulo resty.limit.req
- módulo resty.limit.conn
- módulo resty.limit.count
- módulo resty.limit.traffic
- biblioteca lua-resty-limit-traffic
- o módulo ngx_lua: https://github.com/openresty/lua-nginx-module
- OpenResty: https://openresty.org/
GitHub
Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório do GitHub para nginx-module-limit-rate.