Rate Limiting und Throttling in C#: So schützen Sie Ihre API vor Überlastungen und bewahren die Nerven Ihrer Benutzer
\n\nStellen Sie sich vor: Sie haben eine coole API gestartet. Der Server fliegt, die Datenbank antwortet sofort. Doch plötzlich – peng! – ein unvorsichtiger Client (oder, noch schlimmer, ein DDoS-Angriff) beginnt, Ihren Endpunkt mit tausenden Anfragen pro Sekunde zu bombardieren. Der Server fällt aus, die Datenbank erstickt, und die echten Benutzer sehen einen 503-Fehler. Kommt Ihnen das bekannt vor?
\n\nDie Lösung für dieses Problem sind Rate Limiting (Geschwindigkeitsbegrenzung) und Throttling (Drosselung). In C# haben wir alle Werkzeuge, um diese Mechanismen elegant und effizient zu implementieren. In diesem Artikel gehen wir nicht nur die Theorie durch – wir schreiben echten Code, analysieren gängige Algorithmen und finden heraus, was für die jeweilige Aufgabe am besten geeignet ist. Schnallen Sie sich an, es wird heiß!
\n\nRate Limiting vs Throttling: Was ist der Unterschied?
\n\nObwohl diese Begriffe oft verwechselt werden, gibt es eine klare Grenze zwischen ihnen:
\n- \n
- Rate Limiting – eine „harte“ Blockierung. Wenn ein Benutzer das Limit überschreitet (z. B. 100 Anfragen pro Minute), gibt der Server HTTP 429 (Too Many Requests) zurück. Die Anfragen erreichen die Geschäftslogik gar nicht erst. \n
- Throttling – eine „weiche“ Verlangsamung. Der Server nimmt die Anfrage an, verarbeitet sie jedoch mit Verzögerung oder senkt die Priorität. Der Benutzer erhält keinen Fehler, aber seine Arbeitsgeschwindigkeit sinkt. \n
In C# können wir beide Ansätze implementieren und oft für verschiedene Szenarien kombinieren.
\n\nRate-Limiting-Algorithmen: Vom Einfachen zum Komplexen
\n\nBevor wir Code schreiben, lassen Sie uns die Mathematik verstehen. Es gibt vier „goldene“ Algorithmen, die 90 % der Lösungen zugrunde liegen.
\n\n1. Fixed Window (Festes Fenster)
\nDer einfachste Weg. Wir teilen die Zeit in feste Intervalle (z. B. 1 Minute) und zählen die Anfragen in jedem Fenster. Sobald der Zähler das Limit erreicht – blockieren wir.
\nVorteile: Einfach zu implementieren. Nachteile: Problem des „Randeffekts“ (Burst beim Übergang zwischen Fenstern).
\n\npublic class FixedWindowRateLimiter\n{\n private readonly int _limit;\n private readonly TimeSpan _windowSize;\n private int _counter;\n private DateTime _windowStart;\n\n public FixedWindowRateLimiter(int limit, TimeSpan windowSize)\n {\n _limit = limit;\n _windowSize = windowSize;\n _windowStart = DateTime.UtcNow;\n }\n\n public bool AllowRequest()\n {\n var now = DateTime.UtcNow;\n if (now - _windowStart >= _windowSize)\n {\n // Fenster zurücksetzen\n _counter = 0;\n _windowStart = now;\n }\n\n if (_counter >= _limit)\n return false; // Too Many Requests\n\n Interlocked.Increment(ref _counter);\n return true;\n }\n}\n\n2. Sliding Window (Gleitendes Fenster)
\nWeiterentwicklung des Fixed Window. Anstelle fester Grenzen berücksichtigen wir Anfragen in einem gleitenden Zeitabschnitt. Üblicherweise durch Protokollierung von Zeitstempeln (z. B. in einem Ringpuffer) implementiert.
\nVorteile: Glättet Spitzen. Nachteile: Etwas komplexer in der Implementierung, erfordert Speicher für die Speicherung der Zeitstempel.
\n\npublic class SlidingWindowRateLimiter\n{\n private readonly int _limit;\n private readonly TimeSpan _windowSize;\n private readonly Queue<DateTime> _timestamps = new();\n\n public SlidingWindowRateLimiter(int limit, TimeSpan windowSize)\n {\n _limit = limit;\n _windowSize = windowSize;\n }\n\n public bool AllowRequest()\n {\n var now = DateTime.UtcNow;\n lock (_timestamps)\n {\n // Veraltete Zeitstempel entfernen\n while (_timestamps.Count > 0 && now - _timestamps.Peek() > _windowSize)\n _timestamps.Dequeue();\n\n if (_timestamps.Count >= _limit)\n return false;\n\n _timestamps.Enqueue(now);\n return true