Zum Inhalt

limit-rate: Lua-Modul zur Begrenzung der Anforderungsrate für nginx-module-lua, unter Verwendung der "Token-Bucket"-Methode

Installation

Wenn Sie noch kein RPM-Repository-Abonnement eingerichtet haben, melden Sie sich an. Dann können Sie mit den folgenden Schritten fortfahren.

CentOS/RHEL 7 oder 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

Um diese Lua-Bibliothek mit NGINX zu verwenden, stellen Sie sicher, dass nginx-module-lua installiert ist.

Dieses Dokument beschreibt lua-resty-limit-rate v0.1, veröffentlicht am 25. Oktober 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, -- verwende lua-resty-lock
                    locks_shdict_name = "my_locks",
                })

                if not lim then
                    ngx.log(ngx.ERR,
                            "Fehler beim Instanziieren eines resty.limit.rate-Objekts: ", err)
                    return ngx.exit(500)
                end

                -- der folgende Aufruf muss pro Anfrage erfolgen.
                -- hier verwenden wir die Remote-(IP)-Adresse als das begrenzende Schlüssel
                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, "Fehler beim Entnehmen des Tokens: ", err)
                    return ngx.exit(500)
                end

                if delay >= 0.001 then
                    -- der 2. Rückgabewert enthält die aktuelle Anzahl verfügbarer Tokens
                    -- von Anfragen für den angegebenen Schlüssel
                    local avail = err

                    ngx.sleep(delay)
                end
            }

            # Inhaltshandler kommt hierhin. Wenn es content_by_lua ist, können Sie
            # den obigen Lua-Code in access_by_lua in den Lua-Handler Ihres content_by_lua
            # integrieren, um ein wenig CPU-Zeit zu sparen.
        }

        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, "Fehler beim Entnehmen von global: ", err)
                    return ngx.exit(500)
                end

                -- hier verwenden wir die Benutzer-ID als den begrenzenden Schlüssel
                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, "Fehler beim Entnehmen von single: ", err)
                    return ngx.exit(500)
                end

                if t0 == 1 then
                    return -- globaler Bucket ist nicht hungrig
                else
                    if t1 == 1 then
                        return -- einzelner Bucket ist nicht hungrig
                    else
                        return ngx.exit(503)
                    end
                end
            }
        }
    }
}

Beschreibung

Dieses Modul bietet APIs, um Benutzern von OpenResty/ngx_lua zu helfen, die Anforderungsrate mithilfe der "Token-Bucket"-Methode zu begrenzen.

Wenn Sie mehrere verschiedene Instanzen dieser Klasse gleichzeitig verwenden oder eine Instanz dieser Klasse mit Instanzen anderer Klassen (wie resty.limit.conn) verwenden möchten, müssen Sie das resty.limit.traffic Modul verwenden, um sie zu kombinieren.

Der Hauptunterschied zwischen diesem Modul und resty.limit.req:

  • resty.limit.req begrenzt die Anforderungsrate mithilfe der "leaky bucket"-Methode, während dieses Modul die "Token-Bucket"-Methode verwendet.

Der Hauptunterschied zwischen diesem Modul und resty.limit.count:

  • resty.limit.count bietet ein einfaches mentales Modell, das die Anforderungsrate durch eine feste Anzahl von Anfragen in einem bestimmten Zeitfenster begrenzt, aber manchmal kann es die doppelte Anzahl der erlaubten Anfragen pro Minute durchlassen. Zum Beispiel, wenn unsere Ratenbegrenzung 10 Anfragen pro Minute beträgt und ein Benutzer um 10:00:59 10 Anfragen stellt, könnte er um 10:01:00 10 weitere Anfragen stellen, da ein neuer Zähler zu Beginn jeder Minute beginnt. In diesem Fall kann dieses Modul präziser und reibungsloser steuern.

Methoden

new

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

Instanziiert ein Objekt dieser Klasse. Der class-Wert wird durch den Aufruf require "resty.limit.rate" zurückgegeben.

Die Methode gibt einen neuen Token-Bucket zurück, der mit einer Rate von quantum Tokens alle interval füllt, bis zur angegebenen maximalen capacity. Der Bucket ist anfangs voll.

Diese Methode nimmt die folgenden Argumente und eine optionale Options-Tabelle opts entgegen:

  • shdict_name ist der Name des lua_shared_dict shm-Bereichs.

    Es ist eine bewährte Methode, separate shm-Bereiche für verschiedene Arten von Limitierern zu verwenden.

  • interval ist die Zeitspanne zwischen dem Hinzufügen von Tokens in Millisekunden.

  • capacity ist die maximale Anzahl von Tokens, die im Bucket gehalten werden können.

  • quantum ist die Anzahl von Tokens, die in einem Intervall zum Bucket hinzugefügt werden, dieses Argument ist optional, Standardwert ist 1.

  • max_wait ist die maximale Zeit, die wir warten würden, bis genügend Tokens hinzugefügt werden, in Millisekunden, dieses Argument ist optional, Standardwert ist nil, was Unendlichkeit bedeutet.

Die Options-Tabelle akzeptiert die folgenden Optionen:

  • lock_enable Wenn aktiviert, wird der Zustand des shm über mehrere nginx-Arbeitsprozesse hinweg atomar aktualisiert; andernfalls gibt es ein (kleines) Zeitfenster für Rennbedingungen zwischen dem Verhalten "lesen-und-dann-schreiben", Standardwert ist false. Weitere Details finden Sie in lua-resty-lock.

  • locks_shdict_name Gibt den Namen des gemeinsamen Wörterbuchs (erstellt durch lua_shared_dict) für das Lock an, Standardwert ist locks.

Im Fehlerfall gibt diese Methode nil und eine Zeichenfolge zurück, die den Fehler beschreibt (wie einen ungültigen lua_shared_dict-Namen).

incoming

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

Löst ein neues Ereignis für eingehende Anfragen aus und berechnet die erforderliche Verzögerung (falls vorhanden) für die aktuelle Anfrage basierend auf dem angegebenen Schlüssel oder ob der Benutzer sie sofort ablehnen sollte.

Ähnlich wie die take Methode, aber diese Methode entnimmt nur ein Token aus dem Bucket auf einmal.

Diese Methode akzeptiert die folgenden Argumente:

  • key ist der vom Benutzer angegebene Schlüssel zur Begrenzung der Rate.

    Bitte beachten Sie, dass dieses Modul den Benutzer-Schlüssel weder vor noch nachanfügt, sodass es in der Verantwortung des Benutzers liegt, sicherzustellen, dass der Schlüssel im lua_shared_dict shm-Bereich eindeutig ist.

  • commit ist ein boolescher Wert. Wenn auf true gesetzt, wird das Ereignis tatsächlich im shm-Bereich, der das aktuelle Objekt unterstützt, aufgezeichnet; andernfalls wäre es nur ein "Trockenlauf" (was der Standard ist).

set_max_wait

Syntax: obj:set_max_wait(max_wait?)

Überschreibt den max_wait-Schwellenwert, wie im new Methode angegeben.

take

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

Die Methode entnimmt count Tokens aus dem Bucket, ohne zu blockieren.

Diese Methode akzeptiert die folgenden Argumente:

  • key ist der vom Benutzer angegebene Schlüssel zur Begrenzung der Rate.

    Bitte beachten Sie, dass dieses Modul den Benutzer-Schlüssel weder vor noch nachanfügt, sodass es in der Verantwortung des Benutzers liegt, sicherzustellen, dass der Schlüssel im lua_shared_dict shm-Bereich eindeutig ist.

  • count ist die Anzahl der zu entnehmenden Tokens.

  • commit ist ein boolescher Wert. Wenn auf true gesetzt, wird das Ereignis tatsächlich im shm-Bereich, der das aktuelle Objekt unterstützt, aufgezeichnet; andernfalls wäre es nur ein "Trockenlauf" (was der Standard ist).

Die Rückgabewerte hängen von den folgenden Fällen ab:

  1. Wenn der max_wait-Wert, der in der new oder set_max_wait Methode angegeben ist, wird die Methode nur Tokens aus dem Bucket entnehmen, wenn die Wartezeit für die Tokens nicht größer als max_wait ist, und gibt die Zeit zurück, die der Aufrufer warten sollte, bis die Tokens tatsächlich verfügbar sind, andernfalls gibt sie nil und die Fehlermeldung "rejected" zurück.

  2. Wenn der max_wait-Wert nil ist, gibt er die Zeit zurück, die der Aufrufer warten sollte, bis die Tokens tatsächlich verfügbar sind.

Darüber hinaus gibt diese Methode auch einen zweiten Rückgabewert zurück, der die Anzahl der aktuell verfügbaren Tokens zu diesem Zeitpunkt angibt.

Wenn ein Fehler aufgetreten ist (wie z. B. Fehler beim Zugriff auf den lua_shared_dict shm-Bereich, der das aktuelle Objekt unterstützt), gibt diese Methode nil und eine Zeichenfolge zurück, die den Fehler beschreibt.

Diese Methode schläft niemals selbst. Sie gibt einfach eine Verzögerung zurück, falls erforderlich, und erfordert, dass der Aufrufer später die ngx.sleep Methode aufruft, um zu schlafen.

take_available

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

Die Methode entnimmt bis zu count sofort verfügbaren Tokens aus dem Bucket. Sie gibt die Anzahl der entnommenen Tokens zurück oder null, wenn keine verfügbaren Tokens vorhanden sind. Sie blockiert nicht.

Diese Methode akzeptiert die folgenden Argumente:

  • key ist der vom Benutzer angegebene Schlüssel zur Begrenzung der Rate.

    Bitte beachten Sie, dass dieses Modul den Benutzer-Schlüssel weder vor noch nachanfügt, sodass es in der Verantwortung des Benutzers liegt, sicherzustellen, dass der Schlüssel im lua_shared_dict shm-Bereich eindeutig ist.

  • count ist die Anzahl der zu entnehmenden Tokens.

Wenn ein Fehler aufgetreten ist (wie z. B. Fehler beim Zugriff auf den lua_shared_dict shm-Bereich, der das aktuelle Objekt unterstützt), gibt diese Methode nil und eine Zeichenfolge zurück, die den Fehler beschreibt.

uncommit

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

Dies versucht, das Commit des incoming-Aufrufs rückgängig zu machen. Dies ist einfach eine Annäherung und sollte mit Vorsicht verwendet werden. Diese Methode ist hauptsächlich für die Verwendung im resty.limit.traffic Lua-Modul gedacht, wenn mehrere Limitierer gleichzeitig kombiniert werden.

Begrenzungsgranularität

Die Begrenzung funktioniert auf der Granularität einer einzelnen NGINX-Serverinstanz (einschließlich aller ihrer Arbeitsprozesse). Dank des shm-Mechanismus können wir den Zustand kostengünstig über alle Worker in einer einzelnen NGINX-Serverinstanz teilen.

Siehe auch

GitHub

Sie finden möglicherweise zusätzliche Konfigurationstipps und Dokumentationen für dieses Modul im GitHub-Repository für nginx-module-limit-rate.