Ratenbegrenzung, auch bekannt als Anforderungsdrosselung (request throttling), ist ein Mechanismus zur Kontrolle der Rate, mit der ein Benutzer oder Dienst Anfragen an eine API oder einen Server senden kann, um Missbrauch zu verhindern, eine faire Ressourcenzuweisung zu gewährleisten und die Systemstabilität aufrechtzuerhalten.
Ratenbegrenzung verstehen
Die Ratenbegrenzung schützt Dienste vor übermäßigen Anforderungsvolumina, die zu Leistungseinbußen, Denial-of-Service (DoS)-Angriffen oder Ressourcenerschöpfung führen könnten. Sie stellt sicher, dass Systemressourcen allen legitimen Benutzern zur Verfügung stehen und verhindert, dass eine einzelne Entität den Zugriff monopolisiert. Ein Proxy-Dienst spielt oft eine entscheidende Rolle bei der Durchsetzung dieser Limits, indem er sie entweder auf Client-Anfragen anwendet, bevor diese Upstream-Dienste erreichen, oder indem er Upstream-Limits an die Clients zurückkommuniziert.
Warum Ratenbegrenzung implementieren?
- Ressourcenschutz: Verhindert, dass Server durch zu viele Anfragen überlastet werden, und schont CPU, Speicher und Netzwerkbandbreite.
- Missbrauchsprävention: Mindert Brute-Force-Angriffe, Credential Stuffing und andere böswillige Aktivitäten durch Begrenzung der Anforderungsversuche.
- Faire Nutzung: Stellt sicher, dass alle Clients gleichberechtigten Zugriff auf gemeinsam genutzte Ressourcen erhalten, und verhindert, dass ein einzelner Client das System monopolisiert.
- Kostenkontrolle: Bei Diensten mit nutzungsbasierter Abrechnung können Ratenbegrenzungen helfen, die Betriebskosten durch Begrenzung des Ressourcenverbrauchs zu kontrollieren.
Gängige Ratenbegrenzungsalgorithmen
Es werden verschiedene Algorithmen eingesetzt, um Ratenbegrenzungen zu verfolgen und durchzusetzen, jeder mit unterschiedlichen Eigenschaften hinsichtlich der Handhabung von Bursts und der Ressourcennutzung.
Token Bucket
Der Token-Bucket-Algorithmus modelliert einen Eimer mit einer festen Kapazität, der sich mit einer konstanten Rate mit Tokens füllt. Jede Anfrage verbraucht ein Token. Ist der Eimer leer, wird die Anfrage abgelehnt oder in die Warteschlange gestellt. Dieser Algorithmus ermöglicht eine gewisse Burstiness, da Anfragen mehrere Tokens verbrauchen können, falls verfügbar, bis zur Kapazität des Eimers.
Leaky Bucket
Der Leaky-Bucket-Algorithmus verarbeitet Anfragen mit einer festen Ausgaberate. Anfragen werden einer Warteschlange (dem "Eimer") hinzugefügt. Ist die Warteschlange voll, werden neue Anfragen abgelehnt. Anfragen "sickern" mit einer konstanten Rate aus dem Eimer, was einen stetigen Verarbeitungsfluss gewährleistet. Dieser Algorithmus glättet Bursts, führt aber Latenz für Anfragen ein, die in der Warteschlange warten müssen.
Fixed Window Counter
Beim Fixed Window Counter-Algorithmus wird ein Zeitfenster (z.B. 60 Sekunden) definiert, und ein Zähler verfolgt Anfragen innerhalb dieses Fensters. Sobald das Fenster abläuft, wird der Zähler zurückgesetzt. Anfragen, die das Limit innerhalb des Fensters überschreiten, werden abgelehnt. Ein Nachteil ist das "Burst-Problem" an den Fenstergrenzen, bei dem Clients möglicherweise die doppelte Anzahl der erlaubten Anfragen über zwei aufeinanderfolgende Fenster senden.
Sliding Window Log
Der Sliding Window Log-Algorithmus speichert einen Zeitstempel für jede Anfrage. Wenn eine neue Anfrage eingeht, zählt das System die Anzahl der Zeitstempel innerhalb der letzten N Sekunden (das Fenster). Überschreitet diese Anzahl das Limit, wird die Anfrage abgelehnt. Diese Methode ist genau, kann aber aufgrund der Speicherung aller Zeitstempel speicherintensiv sein.
Sliding Window Counter
Dieser Algorithmus kombiniert Aspekte von Fixed Window und Sliding Window Log, um das Randproblem ohne den Speicher-Overhead der Protokollierung jeder Anfrage zu mindern. Er verwendet zwei feste Fenster: das aktuelle Fenster und das vorherige Fenster. Der Zeitstempel der aktuellen Anfrage bestimmt ihre Position innerhalb des aktuellen Fensters. Die erlaubten Anfragen werden als gewichteter Durchschnitt der Zählung des vorherigen Fensters und der Zählung des aktuellen Fensters berechnet, basierend auf dem Anteil des aktuellen Fensters, der verstrichen ist.
Algorithmus-Vergleich
| Merkmal | Token Bucket | Leaky Bucket | Fixed Window Counter | Sliding Window Log | Sliding Window Counter |
|---|---|---|---|---|---|
| Burst-Handling | Erlaubt Bursts | Glättet Bursts | Anfällig für Bursts | Handhabt Bursts gut | Handhabt Bursts gut |
| Ressourcennutzung | Moderat | Moderat | Gering | Hoch (Speicher) | Gering bis Moderat |
| Komplexität | Moderat | Moderat | Gering | Hoch | Moderat |
| Genauigkeit | Gut | Gut | Schlecht (Grenzfälle) | Hoch | Gut |
| Latenz-Auswirkung | Gering (wenn Tokens existieren) | Hoch (Warteschlange) | Gering | Gering | Gering |
Ratenbegrenzung identifizieren
Wenn eine Ratenbegrenzung überschritten wird, antwortet eine API oder ein Dienst typischerweise mit spezifischen HTTP-Statuscodes und Headern.
HTTP-Statuscode 429 Too Many Requests
Der Standard-HTTP-Statuscode für Ratenbegrenzung ist 429 Too Many Requests. Dies zeigt an, dass der Benutzer in einem bestimmten Zeitraum zu viele Anfragen gesendet hat.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{
"error": "Rate limit exceeded. Try again in 30 seconds."
}
Antwort-Header
APIs enthalten oft spezifische Header, um mehr Kontext über den Status der Ratenbegrenzung und deren Handhabung zu liefern.
Retry-After: (RFC 7231, Abschnitt 7.1.3) Gibt an, wie lange der User Agent warten soll, bevor er eine Folgeanfrage stellt. Sein Wert kann eine ganze Zahl sein, die Sekunden darstellt, oder ein bestimmtes Datum/Uhrzeit.X-RateLimit-Limit: Die maximale Anzahl von Anfragen, die im aktuellen Ratenbegrenzungsfenster erlaubt sind.X-RateLimit-Remaining: Die Anzahl der verbleibenden Anfragen im aktuellen Ratenbegrenzungsfenster.X-RateLimit-Reset: Die Zeit (normalerweise Unix-Epoch-Sekunden), zu der das aktuelle Ratenbegrenzungsfenster zurückgesetzt wird.
Diese X-RateLimit-*-Header sind üblich, aber nicht durch einen RFC standardisiert; ihre genaue Benennung und ihr Verhalten können zwischen den Diensten variieren.
Ratenbegrenzungen handhaben
Eine effektive clientseitige Handhabung von Ratenbegrenzungen ist entscheidend für den Aufbau robuster Anwendungen, die mit externen Diensten interagieren.
Exponentielles Backoff mit Jitter
Dies ist eine Standardstrategie zum Wiederholen fehlgeschlagener Anfragen, einschließlich derer aufgrund von Ratenbegrenzung.
- Exponentielles Backoff: Der Client wartet eine exponentiell ansteigende Zeitspanne zwischen den Wiederholungsversuchen (z.B. 1 Sekunde, dann 2 Sekunden, dann 4 Sekunden, dann 8 Sekunden).
- Jitter: Eine kleine zufällige Verzögerung wird zur Backoff-Periode hinzugefügt. Dies verhindert, dass alle Clients gleichzeitig nach einem Ratenbegrenzungs-Reset wiederholen, was eine weitere Welle von Ratenbegrenzungen auslösen könnte.
import time
import random
import requests
def make_request_with_retry(url, max_retries=5):
retries = 0
while retries < max_retries:
try:
response = requests.get(url)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 1))
print(f"Ratenbegrenzung erreicht. Wiederholung nach {retry_after} Sekunden.")
time.sleep(retry_after)
elif 200 <= response.status_code < 300:
return response
else:
response.raise_for_status() # Löst eine Ausnahme für andere HTTP-Fehler aus
except requests.exceptions.RequestException as e:
print(f"Anfrage fehlgeschlagen: {e}")
# Exponentielles Backoff mit Jitter
delay = (2 ** retries) + random.uniform(0, 1) # 2^retries + zufällige Gleitkommazahl zwischen 0 und 1
print(f"Wiederholung in {delay:.2f} Sekunden...")
time.sleep(delay)
retries += 1
raise Exception(f"Anfrage nach {max_retries} Wiederholungen fehlgeschlagen.")
# Beispielnutzung:
# response = make_request_with_retry("https://api.example.com/data")
# if response:
# print("Anfrage erfolgreich:", response.json())
Retry-After-Header respektieren
Wenn eine API einen Retry-After-Header bereitstellt, müssen Clients diese Anweisung befolgen. Der Wert gibt die Mindestzeit an, die gewartet werden muss, bevor eine weitere Anfrage an denselben Endpunkt gesendet wird.
Clientseitiges Caching
Antworten von häufig aufgerufenen, nicht-flüchtigen Endpunkten zwischenspeichern. Dies reduziert die Anzahl der an die API gesendeten Anfragen und hilft indirekt, innerhalb der Ratenbegrenzungen zu bleiben.
Anfragen bündeln (Batching)
Wenn die API dies unterstützt, mehrere kleinere Operationen zu einer einzigen, größeren Anfrage zusammenfassen. Dies reduziert die Gesamtzahl der API-Aufrufe.
Prädiktive Drosselung
Clients können ihre eigene Anfragerate überwachen und Anfragen proaktiv verlangsamen oder pausieren, wenn sie sich bekannten Ratenbegrenzungen nähern, anstatt auf eine 429-Antwort zu warten. Dies erfordert die vorherige Kenntnis der Ratenbegrenzungen der API.
Proxy-Dienstkonfiguration für Ratenbegrenzung
Ein robuster Proxy-Dienst bietet umfassende Funktionen zur Verwaltung von Ratenbegrenzungen, sowohl für Clients, die über den Proxy auf Dienste zugreifen, als auch für die eigenen Interaktionen des Proxys mit Upstream-APIs.
Durchsetzung von Limits für eingehenden Traffic (Ingress)
Der Proxy kann Ratenbegrenzungen auf eingehende Client-Anfragen basierend auf verschiedenen Kriterien anwenden.
- Client-IP-Adresse: Begrenzt Anfragen von einer einzelnen IP.
- API-Schlüssel/Token: Begrenzt Anfragen, die mit einem bestimmten Authentifizierungsnachweis verbunden sind.
- Benutzer-ID: Wenn der Proxy Benutzerinformationen aus Headern oder Tokens extrahieren kann.
- Pfad/Endpunkt: Unterschiedliche Ratenbegrenzungen für verschiedene API-Endpunkte (z.B.
/searchkönnte ein höheres Limit haben als/admin/delete).
# Beispiel: Proxy-Konfiguration für Ratenbegrenzung nach IP
http:
routers:
api-router:
rule: "Host(`api.example.com`)"
service: api-service
middlewares: [rate-limit-ip]
middlewares:
rate-limit-ip:
rateLimit:
average: 100 # Anfragen pro Sekunde
burst: 50 # maximale Überschreitung des Durchschnitts
sourceCriterion: "ipStrategy" # Limit pro Quell-IP anwenden
Verwaltung des ausgehenden Traffics (Egress) zu Upstream-Diensten
Wenn der Proxy selbst Upstream-APIs konsumiert, kann er seine eigene Ratenbegrenzung implementieren, um eine Überlastung dieser externen Dienste zu verhindern. Dies ist entscheidend für Integrationsszenarien, in denen der Proxy Daten aus mehreren Quellen aggregiert.
- Upstream-spezifische Limits: Konfigurieren Sie unterschiedliche Ratenbegrenzungen für jeden Upstream-Dienst, mit dem der Proxy kommuniziert.
- Circuit Breaking: Kombinieren Sie Ratenbegrenzung mit Circuit-Breaker-Mustern, um Fehler zu isolieren, wenn ein Upstream-Dienst nicht mehr reagiert oder den Proxy konsistent ratenbegrenzt.
Anpassung und Granularität
Fortgeschrittene Proxy-Konfigurationen ermöglichen eine feingranulare Kontrolle über die Ratenbegrenzung:
- Dynamische Limits: Passen Sie Limits basierend auf der Backend-Gesundheit, der Tageszeit oder anderen Betriebsmetriken an.
- Gestaffelte Limits: Implementieren Sie unterschiedliche Ratenbegrenzungen für verschiedene Client-Stufen (z.B. kostenlose vs. Premium-Benutzer).
- Kontingentverwaltung: Verfolgen Sie die Nutzung anhand längerfristiger Kontingente (z.B. Anfragen pro Monat) zusätzlich zu kurzfristigen Ratenbegrenzungen.
Überwachung und Alarmierung
Ein Proxy-Dienst sollte Tools zur Überwachung von Ratenbegrenzungsstatistiken bereitstellen:
- Anfragezählungen: Verfolgen Sie die Gesamtzahl der Anfragen, erfolgreiche Anfragen und ratenbegrenzte Anfragen.
- Limit-Überschreitungen: Alarmieren Sie, wenn Ratenbegrenzungen für bestimmte Clients oder Upstream-Dienste erreicht oder überschritten werden.
- Nutzungstrends: Visualisieren Sie Anfragemuster über die Zeit, um potenzielle Engpässe oder Missbrauch zu identifizieren.
Die Überwachung hilft den Betriebsteams, Traffic-Muster zu verstehen, Ratenbegrenzungskonfigurationen zu optimieren und Probleme proaktiv anzugehen, bevor sie die Dienstverfügbarkeit beeinträchtigen.