Ratenbegrenzung in Backend-Frameworks – Token Bucket vs. Gleitendes Fenster
Olivia Novak
Dev Intern · Leapcell

Einleitung
In der komplexen Welt der Backend-Entwicklung ist die Aufrechterhaltung der Systemstabilität und Leistung unter wechselnden Lastbedingungen von größter Bedeutung. Eine der effektivsten Strategien hierfür ist die Ratenbegrenzung, ein Mechanismus, der die Häufigkeit von Anfragen steuert, die ein Client innerhalb eines definierten Zeitrahmens an eine API oder einen Dienst stellen kann. Ohne ordnungsgemäße Ratenbegrenzung können bösartige Angriffe wie Denial of Service (DoS) oder sogar unbeabsichtigte Spitzen des legitimen Datenverkehrs die Servicequalität schnell verschlechtern, Ressourcen aufbrauchen und zu Ausfällen führen. Dieser Artikel untersucht zwei grundlegende Algorithmen zur Implementierung von Ratenbegrenzungen in Backend-Frameworks: den Token Bucket und das Gleitende Fenster. Wir werden uns mit ihren Kernprinzipien, praktischen Implementierungen und geeigneten Anwendungsfällen befassen, um ein klares Verständnis dafür zu vermitteln, wie jeder zur Entwicklung robuster und skalierbarer Backend-Systeme beiträgt.
Kernkonzepte für Ratenbegrenzung
Bevor wir uns den Algorithmen zuwenden, definieren wir einige Schlüsselbegriffe, die für das Verständnis der Ratenbegrenzung von entscheidender Bedeutung sind:
- Anfragrate: Die Anzahl der Anfragen, die pro Zeiteinheit verarbeitet werden (z. B. Anfragen pro Sekunde, Anfragen pro Minute).
- Drosseln (Throttle): Die Raten von Anfragen absichtlich begrenzen, um Ressourcenerschöpfung oder Systemüberlastung zu verhindern.
- Kontingent (Quota): Die maximale Anzahl von Anfragen, die einem Client oder Dienst innerhalb eines bestimmten Zeitraums gestattet sind.
- Burst: Ein plötzlicher, kurzzeitiger Anstieg der Anfragrate, der den Durchschnitt übersteigt.
- Granularität: Die feinste Detailebene, auf der Ratenbegrenzungen angewendet werden (z. B. pro Benutzer, pro IP-Adresse, pro API-Endpunkt).
- Verteilte Ratenbegrenzung: Ratenbegrenzung, die über mehrere Instanzen eines Dienstes angewendet wird und einen gemeinsamen Zustand für eine genaue Durchsetzung erfordert.
Token Bucket: Ein flexibler Ansatz zur Ratenbegrenzung
Der Token Bucket-Algorithmus ist eine intuitive und weit verbreitete Methode zur Ratenbegrenzung, die für ihre Fähigkeit bekannt ist, Datenverkehrsspitzen (Bursts) problemlos zu bewältigen.
Prinzip
Stellen Sie sich einen Eimer mit einer festen Kapazität vor. In diesen Eimer werden kontinuierlich Token mit konstanter Rate hinzugefügt. Jedes Mal, wenn ein Client eine Anfrage stellt, versucht er, ein Token aus dem Eimer zu ziehen. Wenn ein Token verfügbar ist, wird die Anfrage verarbeitet und das Token entfernt. Wenn der Eimer leer ist, wird die Anfrage abgelehnt oder in die Warteschlange gestellt. Die Kapazität des Eimers repräsentiert den maximal zulässigen Burst, während die Rate, mit der Token hinzugefügt werden, die durchschnittliche Langzeit-Anfragrate bestimmt.
Implementierung
Die Implementierung eines Token Buckets umfasst typischerweise:
- Eimerekapazität: Eine maximale Anzahl von Token, die der Eimer aufnehmen kann.
- Token-Generierungsrate: Wie oft neue Token zum Eimer hinzugefügt werden (z. B. 1 Token pro Sekunde).
- Aktuelle Token-Anzahl: Die Anzahl der aktuell im Eimer befindlichen Token.
- Letzte Füllzeit: Der Zeitstempel, wann zuletzt Token hinzugefügt oder überprüft wurden.
Lassen Sie uns dies mit einem Pseudocode-Beispiel in einer Python-ähnlichen Syntax veranschaulichen:
import time class TokenBucket: def __init__(self, capacity, fill_rate_tokens_per_second): self.capacity = float(capacity) self.fill_rate = float(fill_rate_tokens_per_second) self.tokens = float(capacity) # Beginnt mit einem vollen Eimer self.last_fill_time = time.time() def allow_request(self, tokens_needed=1): now = time.time() # Token basierend auf verstrichener Zeit und Füllrate hinzufügen self.tokens = min(self.capacity, self.tokens + (now - self.last_fill_time) * self.fill_rate) self.last_fill_time = now if self.tokens >= tokens_needed: self.tokens -= tokens_needed return True else: return False # Beispielverwendung in einem Backend-Framework (z. B. Flask-Decorator) # Dies ist ein vereinfachtes Beispiel, die reale Anwendung würde die Client-Identifizierung (IP, Benutzer-ID) # und möglicherweise einen verteilten Speicher (Redis) für gemeinsame Zustände beinhalten. # In einem Multi-Instanz-Szenario müsste 'bucket_for_client' aus einem # verteilten Cache wie Redis abgerufen werden, um sicherzustellen, dass alle Instanzen den gleichen Zustand teilen. # Zur Vereinfachung verwenden wir hier ein lokales Dictionary. client_buckets = {} def rate_limit_token_bucket(capacity, fill_rate): def decorator(f): def wrapper(*args, **kwargs): # In einer echten API würde die client_id aus Request-Headern oder IP stammen client_id = "default_client" # Zu Demonstrationszwecken if client_id not in client_buckets: client_buckets[client_id] = TokenBucket(capacity, fill_rate) bucket = client_buckets[client_id] if bucket.allow_request(): return f(*args, **kwargs) else: return "Ratenbegrenzung überschritten. Bitte versuchen Sie es später erneut.", 429 return wrapper return decorator # @app.route('/api/data') # @rate_limit_token_bucket(capacity=10, fill_rate=1) # 10 Anfragen Burst, 1 An/Sek. Durchschnitt # def get_data(): # return "Daten erfolgreich abgerufen!"
Anwendungsfälle
Token Bucket eignet sich hervorragend für:
- Bewältigung von Bursts: Seine Fähigkeit, Token zu speichern, ermöglicht es Clients, Anfragen schneller als die durchschnittliche Rate für kurze Zeiträume zu stellen, was bei benutzergesteuerten Interaktionen üblich ist.
- API-Gateways: Begrenzung von Anfragen externer Clients an Backend-Dienste.
- Drosselung bestimmter Benutzer oder Endpunkte: Bereitstellung einer flexiblen Kontingentverwaltung.
Sliding Window Log: Präzision und Fairness
Der Sliding Window Log-Algorithmus bietet einen hochpräzisen und fairen Ansatz zur Ratenbegrenzung und verhindert, dass Bursts über Fensterränder hinweg „versteckt“ werden.
Prinzip
Im Gegensatz zum Token Bucket speichert der Sliding Window Log eine Protokolldatei von Zeitstempeln für jede erfolgreiche Anfrage. Wenn eine neue Anfrage eingeht, verwirft der Algorithmus alle Zeitstempel aus dem Protokoll, die älter sind als die aktuelle Zeit minus der Fensterdauer. Wenn die Anzahl der verbleibenden Zeitstempel im Protokoll unter dem zulässigen Limit liegt, wird die Anfrage zugelassen und ihr Zeitstempel zum Protokoll hinzugefügt. Andernfalls wird die Anfrage abgelehnt.
Implementierung
Der Kern des Sliding Window Log ist eine sortierte Liste von Zeitstempeln.
import time from collections import deque class SlidingWindowLog: def __init__(self, limit, window_seconds): self.limit = limit self.window_seconds = window_seconds # Deque ist effizient für das Hinzufügen/Entfernen von beiden Enden self.requests = deque() def allow_request(self): now = time.time() # Zeitstempel entfernen, die älter als das Fenster sind while self.requests and self.requests[0] <= now - self.window_seconds: self.requests.popleft() if len(self.requests) < self.limit: self.requests.append(now) return True else: return False # Beispielverwendung in einem Backend-Framework (z. B. Flask-Decorator) client_request_logs = {} def rate_limit_sliding_window_log(limit, window_seconds): def decorator(f): def wrapper(*args, **kwargs): client_id = "default_client" # Aus Request-Headern/IP in Echtzeit-App if client_id not in client_request_logs: client_request_logs[client_id] = SlidingWindowLog(limit, window_seconds) window_log = client_request_logs[client_id] if window_log.allow_request(): return f(*args, **kwargs) else: return "Ratenbegrenzung überschritten. Bitte versuchen Sie es später erneut.", 429 return wrapper return decorator # @app.route('/api/report') # @rate_limit_sliding_window_log(limit=5, window_seconds=60) # 5 Anfragen pro Minute # def get_report(): # return "Bericht erfolgreich generiert!"
Obwohl präzise, kann das deque bei hohem Datenverkehr groß werden, mehr Speicher verbrauchen und Leistungsprobleme aufgrund von Listenoperationen verursachen. Für verteilte Systeme ist die Speicherung dieser Protokolle in Redis Sorted Sets ein gängiger Ansatz, bei dem ZREMRANGEBYSCORE alte Zeitstempel effizient bereinigen kann.
Anwendungsfälle
Sliding Window Log eignet sich für:
- Strikte Ratenbegrenzung: Wenn es entscheidend ist, eine präzise Rate über einen definierten Zeitraum durchzusetzen und jede Form von „Schummeln“ über Fensterränder hinweg zu verhindern.
- Abrechnung und Nutzungsnachverfolgung: Genaue Erfassung der API-Nutzung für Abrechnungszwecke.
- Sicherheitsanwendungen: Erkennung von abnormalen Anfrage Mustern, die auf Angriffe hindeuten könnten.
Vergleich von Token Bucket und Sliding Window Log
| Merkmal | Token Bucket | Sliding Window Log |
|---|---|---|
| Burst-Handling | Exzellent (wegen gespeicherter Token) | Schlecht (strikte Durchsetzung über die Zeit) |
| Fairness | Weniger fair für kurze Bursts (kann Token erschöpfen) | Hochgradig fair (berücksichtigt tatsächliche Anfragezeiten) |
| Genauigkeit | Gut für die Durchschnittsrate, kann über kurze Zeiträume für Bursts undicht sein | Exzellent, sehr präzise über das Fenster |
| Ressourcennutzung | Gering (fester Zähler und Zeitstempel pro Client) | Mittel bis hoch (speichert Zeitstempel pro Client) |
| Komplexität | Einfacher zu implementieren | Komplexer, insbesondere in verteilten Systemen (erfordert persistenten Speicher für das Protokoll) |
| Anwendungsfälle | Allgemeine API-Drosselung, auf Benutzererfahrung fokussiert | Abrechnung, strikte Kontingentdurchsetzung, Betrugserkennung |
Fazit
Sowohl Token Bucket als auch Sliding Window Log sind robuste Algorithmen zur Implementierung von Ratenbegrenzungen, die jeweils ihre Stärken und Schwächen haben. Der Token Bucket bietet Flexibilität und exzellentes Burst-Handling, was ihn ideal für allgemeine API-Nutzung macht, bei der gelegentliche Traffic-Spitzen erwartet werden und ohne schwerwiegende Auswirkungen bewältigt werden sollten. Das Sliding Window Log hingegen bietet überlegene Genauigkeit und Fairness, indem es sich streng an die definierte Rate über ein kontinuierliches Fenster hält, was es besser für Szenarien geeignet macht, die präzise Nutzungsnachverfolgung und strikte Durchsetzung erfordern. Die Wahl des richtigen Algorithmus hängt von den spezifischen Anforderungen Ihres Backend-Dienstes ab, wobei entweder Burst-Toleranz oder strikte Raten-Genauigkeit priorisiert wird. Eine gut durchdachte Strategie zur Ratenbegrenzung beinhaltet oft eine Kombination dieser und anderer Techniken, um ein robustes und performantes System zu schaffen.

