Wenn Rust auf Überlastung trifft: Warum Ihr Dienst „abstürzt“?
\n\nStellen Sie sich vor, Sie haben einen perfekten hochbelastbaren Dienst in Rust geschrieben. Er fliegt, nutzt alle Vorteile der Zero-Cost-Abstraktionen und des Speicherbesitzes. Aber das Problem ist – ein „gieriger“ Client kommt und beginnt, Ihren Server mit Tausenden von Anfragen pro Sekunde zu bombardieren. Was passiert? Stürzt die Datenbank ab? Erstickt die Warteschlange? Oder, noch schlimmer, Sie werden von der externen API gesperrt, die Sie selbst aufrufen?
\n\nIn Rust, wie in jeder anderen Sprache auch, kommt man ohne Rate Limiting (Geschwindigkeitsbegrenzung) und Throttling (Drosselung) nicht aus. Das ist nicht nur eine „gute Praxis“ – es ist eine Frage des Überlebens Ihrer Anwendung in der Produktion. Aber Rust hat eine Besonderheit: Wir lieben es, alles effizient und von Grund auf neu zu machen. Allerdings müssen wir das Rad nicht neu erfinden – wir haben leistungsstarke Crates (Bibliotheken) und elegante Muster.
\n\nIn diesem Artikel werden wir uns ansehen, wie man Anfragebegrenzung in Rust richtig implementiert, sowohl mit klassischen Algorithmen (Token Bucket) als auch mit modernen asynchronen Ansätzen. Sie erfahren, wie Sie Ihre API vor DDoS-Angriffen schützen, wie Sie die Limits externer Dienste nicht überschreiten und wie Sie Ihren eigenen Throttler schreiben, der schneller arbeitet als in Go oder Python.
\n\nRate Limiting vs Throttling: Was ist der Unterschied?
\n\nBevor wir in den Code eintauchen, klären wir ein für alle Mal die Begriffe. Oft werden sie verwechselt, aber der Unterschied ist entscheidend.
\n\n- \n
- Rate Limiting – das ist eine Sicherheitsrichtlinie. Sie sagen: „Client X darf maximal 100 Anfragen pro Minute stellen.“ Wird das Limit überschritten, geben Sie 429 Too Many Requests zurück. Das schützt Ihr Backend. \n
- Throttling – das ist ein Mechanismus zur Geschwindigkeitssteuerung. Sie entscheiden selbst, mit welcher Geschwindigkeit Anfragen verarbeitet werden, um CPU, I/O oder die Datenbank nicht zu überlasten. Der Client kann 1000 Anfragen senden, aber Sie verarbeiten sie mit 10 pro Sekunde, die restlichen werden in eine Warteschlange gestellt. \n
In Rust können beide Ansätze mit denselben Werkzeugen, aber mit unterschiedlicher Logik implementiert werden. Wir werden uns beide ansehen.
\n\nAlgorithmen: Token Bucket und Leaky Bucket
\n\nDie beliebtesten Algorithmen für Rate Limiting sind Token Bucket und Leaky Bucket. Sie sind einfach wie ein Latschen, aber unglaublich effektiv.
\n\nToken Bucket (Eimer mit Token)
\nStellen Sie sich einen Eimer vor, in den jede Sekunde N Token tropfen. Wenn eine Anfrage kommt, nimmt sie einen Token. Ist der Eimer leer, wird die Anfrage abgelehnt. Rust ist dank atomarer Operationen ideal für die Implementierung dieses Algorithmus geeignet.
\n\nLeaky Bucket (Undichter Eimer)
\nHier hat der Eimer ein Loch im Boden, durch das eine festgelegte Anzahl von Anfragen pro Sekunde abfließt. Die Anfragen gelangen von oben in den Eimer. Wenn der Eimer überläuft, werden überschüssige Anfragen verworfen. Dieser Algorithmus eignet sich besser zum Glätten von Lastspitzen.
\n\nIn Rust werden wir diese Algorithmen nicht von Hand schreiben (obwohl es lehrreich wäre). Stattdessen verwenden wir das Crate governor – den Goldstandard für Rate Limiting im Rust-Ökosystem.
Praxis: Governor installieren
\n\nDas Crate governor ist eine schnelle, asynchrone und threadsichere Implementierung von Token Bucket. Es unterstützt sowohl synchrone als auch asynchrone Modi und funktioniert mit tokio und async-std.
Fügen Sie in Cargo.toml hinzu:
[dependencies]\ngovernor = "0.6"\ntokio = { version = "1", features = ["full"] }\n\n\nUnd jetzt schreiben wir einen einfachen Rate Limiter für einen HTTP-Server auf Basis von hyper oder axum. Der Einfachheit halber verwenden wir axum.
use axum::{\n extract::Request,\n http::StatusCode,\n middleware::{self, Next},\n response::Response,\n routing::get,\n Router,\n};\nuse governor::{DefaultDirectRateLimiter, Quota, RateLimiter};\n