limit-rate: módulo Lua para limitar la tasa de solicitudes para nginx-module-lua, utilizando el método "token bucket"
Instalación
Si no has configurado la suscripción al repositorio RPM, regístrate. Luego puedes proceder con los siguientes pasos.
CentOS/RHEL 7 o 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 con NGINX, asegúrate de que nginx-module-lua esté instalado.
Este documento describe lua-resty-limit-rate v0.1 lanzado el 25 de octubre 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,
"falló al instanciar un objeto resty.limit.rate: ", err)
return ngx.exit(500)
end
-- la siguiente llamada debe ser por solicitud.
-- aquí usamos la dirección remota (IP) como la clave de limitación
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, "falló al tomar el token: ", err)
return ngx.exit(500)
end
if delay >= 0.001 then
-- el 2º valor de retorno contiene el número actual de tokens disponibles
-- de solicitudes para la clave especificada
local avail = err
ngx.sleep(delay)
end
}
# el manejador de contenido va aquí. si es content_by_lua, entonces puedes
# fusionar el código Lua anterior en access_by_lua en el manejador de
# content_by_lua para ahorrar un poco de tiempo 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, "falló al tomar global: ", err)
return ngx.exit(500)
end
-- aquí usamos el userid como la clave de limitación
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, "falló al tomar single: ", err)
return ngx.exit(500)
end
if t0 == 1 then
return -- el bucket global no tiene hambre
else
if t1 == 1 then
return -- el bucket single no tiene hambre
else
return ngx.exit(503)
end
end
}
}
}
}
Descripción
Este módulo proporciona APIs para ayudar a los programadores usuarios de OpenResty/ngx_lua a limitar la tasa de solicitudes utilizando el método "token bucket".
Si deseas usar múltiples instancias diferentes de esta clase a la vez o usar una instancia de esta clase con instancias de otras clases (como resty.limit.conn), entonces debes usar el módulo resty.limit.traffic para combinarlos.
La principal diferencia entre este módulo y resty.limit.req:
- resty.limit.req limita la tasa de solicitudes utilizando el método "leaky bucket", este módulo utiliza el método "token bucket".
La principal diferencia entre este módulo y resty.limit.count:
- resty.limit.count ofrece un modelo mental sencillo que limita la tasa de solicitudes por un número fijo de solicitudes en una ventana de tiempo dada, pero a veces puede dejar pasar el doble del número de solicitudes permitidas por minuto. Por ejemplo, si nuestro límite de tasa fuera de 10 solicitudes por minuto y un usuario hiciera 10 solicitudes a las 10:00:59, podría hacer 10 solicitudes más a las 10:01:00 porque un nuevo contador comienza al inicio de cada minuto. En este caso, este módulo puede controlar de manera más precisa y fluida.
Métodos
new
sintaxis: obj, err = class.new(shdict_name, interval, capacity, quantum?, max_wait?, opts?)
Instancia un objeto de esta clase. El valor class es devuelto por la llamada require "resty.limit.rate".
El método devuelve un nuevo token bucket que se llena a la tasa de quantum número de tokens cada interval, hasta la capacidad máxima dada capacity. El bucket está inicialmente lleno.
Este método toma los siguientes argumentos y una tabla de opciones opcional opts:
-
shdict_namees el nombre de la zona shm lua_shared_dict.Es una buena práctica usar zonas shm separadas para diferentes tipos de limitadores.
-
intervales el tiempo que pasa entre la adición de tokens, en milisegundos. -
capacityes el número máximo de tokens que se pueden mantener en el bucket. -
quantumes el número de tokens que se agregarán al bucket en un intervalo, este argumento es opcional, por defecto1. -
max_waites el tiempo máximo que esperaríamos para que se agreguen suficientes tokens, en milisegundos, este argumento es opcional, por defectonil, lo que significa infinito.
La tabla de opciones acepta las siguientes opciones:
-
lock_enableCuando está habilitado, la actualización del estado de shdict a través de múltiples procesos de trabajo de nginx es atómica; de lo contrario, habrá una ventana de condición de carrera (pequeña) entre el comportamiento de "leer-y-luego-escribir", por defectofalse. Consulta lua-resty-lock para más detalles. -
locks_shdict_nameEspecifica el nombre del diccionario compartido (creado por lua_shared_dict) para el bloqueo, por defectolocks.
En caso de fallo, este método devuelve nil y una cadena que describe el error (como un nombre de lua_shared_dict incorrecto).
incoming
sintaxis: delay, err = obj:take(key, commit)
Dispara un nuevo evento de solicitud entrante y calcula el retraso necesario (si lo hay) para la solicitud actual sobre la clave especificada o si el usuario debería rechazarla inmediatamente.
Similar al método take, pero este método solo toma un token del bucket a la vez.
Este método acepta los siguientes argumentos:
-
keyes la clave especificada por el usuario para limitar la tasa.Ten en cuenta que este módulo no antepone ni añade sufijos a la clave del usuario, por lo que es responsabilidad del usuario asegurarse de que la clave sea única en la zona shm de
lua_shared_dict. -
commites un valor booleano. Si se establece entrue, el objeto realmente registrará el evento en la zona shm que respalda el objeto actual; de lo contrario, solo sería una "prueba" (que es el valor predeterminado).
set_max_wait
sintaxis: obj:set_max_wait(max_wait?)
Sobrescribe el umbral max_wait como se especificó en el método new.
take
sintaxis: delay, err = obj:take(key, count, commit)
El método toma count tokens del bucket sin bloquear.
Este método acepta los siguientes argumentos:
-
keyes la clave especificada por el usuario para limitar la tasa.Ten en cuenta que este módulo no antepone ni añade sufijos a la clave del usuario, por lo que es responsabilidad del usuario asegurarse de que la clave sea única en la zona shm de
lua_shared_dict. -
countes el número de tokens a eliminar. -
commites un valor booleano. Si se establece entrue, el objeto realmente registrará el evento en la zona shm que respalda el objeto actual; de lo contrario, solo sería una "prueba" (que es el valor predeterminado).
Los valores de retorno dependen de los siguientes casos:
-
Si el valor
max_waitespecificado en el método new o set_max_wait, el método solo tomará tokens del bucket si el tiempo de espera para los tokens no es mayor quemax_wait, y devuelve el tiempo que el llamador debería esperar hasta que los tokens estén realmente disponibles, de lo contrario, devuelvenily la cadena de error"rejected". -
Si el valor
max_waites nil, devuelve el tiempo que el llamador debería esperar hasta que los tokens estén realmente disponibles.
Además, este método también devuelve un segundo valor de retorno que indica el número de tokens disponibles en este momento.
Si ocurrió un error (como fallos al acceder a la zona shm de lua_shared_dict que respalda el objeto actual), entonces este método devuelve nil y una cadena que describe el error.
Este método nunca duerme por sí mismo. Simplemente devuelve un retraso si es necesario y requiere que el llamador invoque más tarde el método ngx.sleep para dormir.
take_available
sintaxis: count, err = obj:take_available(key, count)
El método toma hasta count tokens disponibles inmediatamente del bucket. Devuelve el número de tokens eliminados, o cero si no hay tokens disponibles. No bloquea.
Este método acepta los siguientes argumentos:
-
keyes la clave especificada por el usuario para limitar la tasa.Ten en cuenta que este módulo no antepone ni añade sufijos a la clave del usuario, por lo que es responsabilidad del usuario asegurarse de que la clave sea única en la zona shm de
lua_shared_dict. -
countes el número de tokens a eliminar.
Si ocurrió un error (como fallos al acceder a la zona shm de lua_shared_dict que respalda el objeto actual), entonces este método devuelve nil y una cadena que describe el error.
uncommit
sintaxis: ok, err = obj:uncommit(key)
Esto intenta deshacer el compromiso de la llamada incoming. Esto es simplemente una aproximación y debe usarse con cuidado. Este método se utiliza principalmente en el módulo Lua resty.limit.traffic al combinar múltiples limitadores al mismo tiempo.
Granularidad de Limitación
La limitación funciona en la granularidad de una instancia individual de servidor NGINX (incluyendo todos sus procesos de trabajo). Gracias al mecanismo shm; podemos compartir el estado de manera económica entre todos los trabajadores en una sola instancia de servidor NGINX.
Ver También
- 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
- el módulo ngx_lua: https://github.com/openresty/lua-nginx-module
- OpenResty: https://openresty.org/
GitHub
Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-limit-rate.