Pular para conteúdo

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  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ão 1.

  • max_wait é o tempo máximo que esperaríamos para que tokens suficientes sejam adicionados, em milissegundos, este argumento é opcional, padrão nil, o que significa infinito.

A tabela de opções aceita as seguintes opções:

  • lock_enable Quando 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ão false. Veja lua-resty-lock para mais detalhes.

  • locks_shdict_name Especifica o nome do dicionário compartilhado (criado por lua_shared_dict) para o bloqueio, padrão locks.

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 como true, 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 como true, 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:

  1. Se o valor max_wait especificado 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 que max_wait, e retorna o tempo que o chamador deve esperar até que os tokens estejam realmente disponíveis, caso contrário, retorna nil e a string de erro "rejected".

  2. Se o valor max_wait for 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

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.