Aller au contenu

limit-rate: module Lua pour limiter le taux de requêtes pour nginx-module-lua, utilisant la méthode "token bucket"

Installation

Si vous n'avez pas encore configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.

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

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.

Ce document décrit lua-resty-limit-rate v0.1 publié le 25 octobre 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, -- utiliser lua-resty-lock
                    locks_shdict_name = "my_locks",
                })

                if not lim then
                    ngx.log(ngx.ERR,
                            "échec de l'instanciation d'un objet resty.limit.rate : ", err)
                    return ngx.exit(500)
                end

                -- l'appel suivant doit être par requête.
                -- ici nous utilisons l'adresse (IP) distante comme clé de limitation
                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, "échec de la prise de token : ", err)
                    return ngx.exit(500)
                end

                if delay >= 0.001 then
                    -- la 2ème valeur de retour contient le nombre actuel de tokens disponibles
                    -- de requêtes pour la clé spécifiée
                    local avail = err

                    ngx.sleep(delay)
                end
            }

            # le gestionnaire de contenu va ici. s'il s'agit de content_by_lua, alors vous pouvez
            # fusionner le code Lua ci-dessus dans access_by_lua dans le gestionnaire Lua de votre content_by_lua
            # pour économiser un peu de temps 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, "échec de la prise globale : ", err)
                    return ngx.exit(500)
                end

                -- ici nous utilisons l'identifiant utilisateur comme clé de limitation
                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, "échec de la prise unique : ", err)
                    return ngx.exit(500)
                end

                if t0 == 1 then
                    return -- le seau global n'est pas affamé
                else
                    if t1 == 1 then
                        return -- le seau unique n'est pas affamé
                    else
                        return ngx.exit(503)
                    end
                end
            }
        }
    }
}

Description

Ce module fournit des API pour aider les programmeurs utilisateurs d'OpenResty/ngx_lua à limiter le taux de requêtes en utilisant la méthode "token bucket".

Si vous souhaitez utiliser plusieurs instances différentes de cette classe en même temps ou utiliser une instance de cette classe avec des instances d'autres classes (comme resty.limit.conn), alors vous devez utiliser le module resty.limit.traffic pour les combiner.

La principale différence entre ce module et resty.limit.req :

  • resty.limit.req limite le taux de requêtes en utilisant la méthode "leaky bucket", ce module utilise la méthode "token bucket".

La principale différence entre ce module et resty.limit.count :

  • resty.limit.count offre un modèle mental simple qui limite le taux de requêtes par un nombre fixe de requêtes dans une fenêtre de temps donnée, mais il peut parfois laisser passer deux fois le nombre de requêtes autorisées par minute. Par exemple, si notre limite de taux était de 10 requêtes par minute et qu'un utilisateur faisait 10 requêtes à 10:00:59, il pourrait faire 10 autres requêtes à 10:01:00 car un nouveau compteur commence au début de chaque minute. Dans ce cas, ce module est capable de contrôler plus précisément et en douceur.

Méthodes

new

syntax: obj, err = class.new(shdict_name, interval, capacity, quantum?, max_wait?, opts?)

Instancie un objet de cette classe. La valeur class est renvoyée par l'appel require "resty.limit.rate".

La méthode renvoie un nouveau seau de tokens qui se remplit au taux de quantum tokens tous les interval, jusqu'à la capacité maximale donnée capacity. Le seau est initialement plein.

Cette méthode prend les arguments suivants et une table d'options facultative opts :

  • shdict_name est le nom de la zone shm lua_shared_dict.

    Il est recommandé d'utiliser des zones shm séparées pour différents types de limiteurs.

  • interval est le temps écoulé entre l'ajout de tokens, en millisecondes.

  • capacity est le nombre maximum de tokens à conserver dans le seau.

  • quantum est le nombre de tokens à ajouter au seau dans un intervalle, cet argument est facultatif, par défaut 1.

  • max_wait est le temps maximum que nous attendrions pour que suffisamment de tokens soient ajoutés, en millisecondes, cet argument est facultatif, par défaut nil, ce qui signifie l'infini.

La table d'options accepte les options suivantes :

  • lock_enable Lorsqu'il est activé, la mise à jour de l'état shdict à travers plusieurs processus de travail nginx est atomique ; sinon, il y aura une fenêtre de condition de course (petite) entre le comportement "lire puis écrire", par défaut false. Voir lua-resty-lock pour plus de détails.

  • locks_shdict_name Spécifie le nom du dictionnaire partagé (créé par lua_shared_dict) pour le verrou, par défaut locks.

En cas d'échec, cette méthode renvoie nil et une chaîne décrivant l'erreur (comme un mauvais nom de lua_shared_dict).

incoming

syntax: delay, err = obj:take(key, commit)

Déclenche un nouvel événement de requête entrante et calcule le délai nécessaire (le cas échéant) pour la requête actuelle sur la clé spécifiée ou si l'utilisateur doit la rejeter immédiatement.

Semblable à la méthode take, mais cette méthode ne prend qu'un seul token du seau à la fois.

Cette méthode accepte les arguments suivants :

  • key est la clé spécifiée par l'utilisateur pour limiter le taux.

    Veuillez noter que ce module ne préfixe ni ne suffixe la clé utilisateur, il est donc de la responsabilité de l'utilisateur de s'assurer que la clé est unique dans la zone shm lua_shared_dict.

  • commit est une valeur booléenne. Si elle est définie sur true, l'objet enregistrera réellement l'événement dans la zone shm soutenant l'objet actuel ; sinon, ce ne serait qu'un "essai à sec" (ce qui est la valeur par défaut).

set_max_wait

syntax: obj:set_max_wait(max_wait?)

Écrase le seuil max_wait tel que spécifié dans la méthode new.

take

syntax: delay, err = obj:take(key, count, commit)

La méthode prend count tokens du seau sans bloquer.

Cette méthode accepte les arguments suivants :

  • key est la clé spécifiée par l'utilisateur pour limiter le taux.

    Veuillez noter que ce module ne préfixe ni ne suffixe la clé utilisateur, il est donc de la responsabilité de l'utilisateur de s'assurer que la clé est unique dans la zone shm lua_shared_dict.

  • count est le nombre de tokens à retirer.

  • commit est une valeur booléenne. Si elle est définie sur true, l'objet enregistrera réellement l'événement dans la zone shm soutenant l'objet actuel ; sinon, ce ne serait qu'un "essai à sec" (ce qui est la valeur par défaut).

Les valeurs de retour dépendent des cas suivants :

  1. Si la valeur max_wait spécifiée dans la méthode new ou set_max_wait, la méthode ne prendra des tokens du seau que si le temps d'attente pour les tokens n'est pas supérieur à max_wait, et renvoie le temps que l'appelant doit attendre jusqu'à ce que les tokens soient réellement disponibles, sinon elle renvoie nil et la chaîne d'erreur "rejected".

  2. Si la valeur max_wait est nil, elle renvoie le temps que l'appelant doit attendre jusqu'à ce que les tokens soient réellement disponibles.

De plus, cette méthode renvoie également une deuxième valeur de retour indiquant le nombre de tokens disponibles à ce moment-là.

Si une erreur s'est produite (comme des échecs lors de l'accès à la zone shm lua_shared_dict soutenant l'objet actuel), alors cette méthode renvoie nil et une chaîne décrivant l'erreur.

Cette méthode ne dort jamais elle-même. Elle renvoie simplement un délai si nécessaire et nécessite que l'appelant invoque plus tard la méthode ngx.sleep pour dormir.

take_available

syntax: count, err = obj:take_available(key, count)

La méthode prend jusqu'à count tokens immédiatement disponibles du seau. Elle renvoie le nombre de tokens retirés, ou zéro s'il n'y a pas de tokens disponibles. Elle ne bloque pas.

Cette méthode accepte les arguments suivants :

  • key est la clé spécifiée par l'utilisateur pour limiter le taux.

    Veuillez noter que ce module ne préfixe ni ne suffixe la clé utilisateur, il est donc de la responsabilité de l'utilisateur de s'assurer que la clé est unique dans la zone shm lua_shared_dict.

  • count est le nombre de tokens à retirer.

Si une erreur s'est produite (comme des échecs lors de l'accès à la zone shm lua_shared_dict soutenant l'objet actuel), alors cette méthode renvoie nil et une chaîne décrivant l'erreur.

uncommit

syntax: ok, err = obj:uncommit(key)

Cela essaie d'annuler l'engagement de l'appel incoming. C'est simplement une approximation et doit être utilisé avec précaution. Cette méthode est principalement destinée à être utilisée dans le module Lua resty.limit.traffic lors de la combinaison de plusieurs limiteurs en même temps.

Granularité de Limitation

La limitation fonctionne sur la granularité d'une instance de serveur NGINX individuelle (y compris tous ses processus de travail). Grâce au mécanisme shm, nous pouvons partager l'état de manière économique entre tous les travailleurs dans une seule instance de serveur NGINX.

Voir Aussi

GitHub

Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-limit-rate.