Rate Limiting
Rate Limiting ist eine grundlegende Technik zur Verkehrssteuerung, die die Anzahl der Anfragen begrenzt, die ein Client innerhalb eines bestimmten Zeitfensters an eine API oder einen Webdienst stellen kann, und so die Infrastruktur vor Überlastung, Denial-of-Service-Angriffen (DoS/DDoS), automatisiertem Missbrauch, böswilligem Scraping und unsachgemäßer Nutzung begrenzter Rechenressourcen schützt. In einem Szenario, in dem moderne APIs Millionen von Anfragen pro Sekunde aus mobilen Anwendungen, Partnerintegrationen, legitimen Bots und potenziell böswilligen Angreifern bedienen, kann das Fehlen eines angemessenen Rate Limitings zu explodierenden Betriebskosten, einer starken Beeinträchtigung der Performance für legitime Nutzer, Anfälligkeiten für Credential-Stuffing- und Brute-Force-Angriffe und sogar zur vollständigen Nichtverfügbarkeit des Dienstes führen. Die effektive Implementierung von Rate Limiting erfordert ein tiefes Verständnis verschiedener Algorithmen (Token Bucket, Leaky Bucket, Fixed Window, Sliding Window), Überlegungen zur verteilten Architektur, bei der mehrere Server Ratenzähler teilen müssen, Strategien zur Client-Identifizierung (IP, API Key, JWT, Session), differenzierte Richtlinien je nach Nutzer-Tier (free, premium, enterprise) und Mechanismen zur klaren Kommunikation über standardisierte HTTP-Header, die Clients über Limits, den aktuellen Verbrauch und die Reset-Zeit informieren. Dieser Artikel untersucht Algorithmen, Implementierungsmuster, Tools und Best Practices für den Aufbau robuster Rate-Limiting-Systeme.
Rate-Limiting-Algorithmen
1. Token Bucket (Am Häufigsten)
# Konzept: Bucket mit maximaler Token-Kapazität
# Tokens werden mit konstanter Rate hinzugefügt
# Jeder Request verbraucht 1 Token
# Wenn der Bucket leer ist, wird der Request abgelehnt
Kapazität: 100 Tokens
Nachfüllrate: 10 Tokens/Sekunde
Vorteile:
- Ermöglicht kontrollierte Bursts (voller Bucket)
- Einfach zu implementieren
- Flexibel für verschiedene Verkehrsmuster
Nachteile:
- Kann Bursts zulassen, die das System überlasten
- Erfordert Tracking des letzten Nachfüllens
Implementierung:
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity;
this.tokens = capacity;
this.refillRate = refillRate;
this.lastRefill = Date.now();
}
consume(count = 1) {
this.refill();
if (this.tokens >= count) {
this.tokens -= count;
return true;
}
return false;
}
refill() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
const tokensToAdd = elapsed * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
2. Leaky Bucket
# Konzept: Queue mit konstantem Abfluss
# Requests gelangen in den Bucket (Queue)
# Mit konstanter Rate verarbeitet (Leak)
# Wenn die Queue voll ist, werden Requests abgelehnt
Vorteile:
- Glättet Bursts (Traffic Shaping)
- Konstante und vorhersehbare Output Rate
- Schützt das Backend vor Spikes
Nachteile:
- Kann Latenz hinzufügen (Queueing)
- Implementierungskomplexität
Verwendung:
- Traffic Shaping
- Network Gateways
- Wenn eine konstante Output Rate kritisch ist
3. Fixed Window Counter
# Konzept: Zähler pro festem Zeitfenster
# Beispiel: 100 Requests pro Minute
# Reset zu Beginn jeder Minute (XX:00, XX:01, XX:02...)
Vorteile:
- Extrem einfach
- Memory efficient
- Leicht zu verstehen
Nachteile:
- Edge Case: 200 Requests in 1 Sekunde
(100 am Ende von Minute 1, 100 am Anfang von Minute 2)
- Erlaubt Bursts an den Fenstergrenzen
Redis-Implementierung:
INCR user:123:2024-01-15:14:30
EXPIRE user:123:2024-01-15:14:30 60
GET user:123:2024-01-15:14:30 # Wenn > 100, reject
4. Sliding Window Log
# Konzept: Log der Request-Timestamps
# Entfernt Requests außerhalb des Fensters
# Zählt Requests innerhalb des gleitenden Fensters
Vorteile:
- Perfekte Genauigkeit
- Keine Edge Cases von Fixed Window
- Verteilt die Rate gleichmäßig
Nachteile:
- Memory intensive (speichert alle Timestamps)
- Performance verschlechtert sich bei High Traffic
Redis-Implementierung (Sorted Set):
ZADD user:123 <timestamp> <request-id>
ZREMRANGEBYSCORE user:123 0 <timestamp-60s> # Entfernt alte
ZCARD user:123 # Count requests
Wenn > 100, reject
5. Sliding Window Counter (Hybrid)
# Konzept: Kombiniert Fixed Window mit Sliding
# Verwendet Zähler vorheriger Fenster mit Gewichtung
# Schätzt die Rate in einem gleitenden Fenster
Beispiel: Limit 100/Minute
Aktuelles Fenster (14:30): 70 Requests
Vorheriges Fenster (14:29): 90 Requests
Verstrichen im aktuellen Fenster: 40s (66.7%)
Schätzung: 90 * (1 - 0.667) + 70 = 30 + 70 = 100
Vorteile:
- Genauigkeit nahe an Sliding Log
- Memory efficient (nur 2 Zähler)
- Glättet Bursts
Nachteile:
- Schätzung (nicht exakt)
- Komplexer als Fixed Window
Distributed Rate Limiting
# Problem: Mehrere Server müssen State teilen
# Lösungen:
1. Zentralisiertes Redis (Am Häufigsten)
const redis = require('redis');
const client = redis.createClient();
async function checkRateLimit(userId) {
const key = \`rate:\$:\${getCurrentWindow()}\`;
const count = await client.incr(key);
if (count === 1) {
await client.expire(key, 60); // 60 seconds
}
return count <= 100; // Limit: 100/min
}
2. Redis Lua Script (Atomic)
const luaScript = \`
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = redis.call('incr', key)
if current == 1 then
redis.call('expire', key, ARGV[2])
end
if current > limit then
return 0
end
return 1
\`;
3. Sticky Sessions + Local Counters
- Nutzer immer zum selben Server routen
- Lokaler Counter auf dem Server
- Problem: Funktioniert nicht gut mit Auto-Scaling
4. Gossip Protocol
- Server teilen State via Gossip
- Eventual Consistency
- Komplexer, eingesetzt bei High Scale
Standard-HTTP-Header
# Standards (RFCs)
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640000000 # Unix timestamp
# Wenn das Limit überschritten wird
HTTP/1.1 429 Too Many Requests
Retry-After: 60 # Sekunden bis zum Retry
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640000060
{
"error": "Rate limit exceeded",
"retryAfter": 60,
"limit": 100
}
# GitHub-Stil (informativer)
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
X-RateLimit-Reset: 1372700873
X-RateLimit-Used: 1
X-RateLimit-Resource: core
Implementierung mit Express.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
const client = redis.createClient();
// Basic rate limiter
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false,
message: 'Too many requests, please try again later.'
});
app.use('/api/', limiter);
// Redis-based distributed rate limiting
const distributedLimiter = rateLimit({
store: new RedisStore({
client: client,
prefix: 'rate-limit:',
}),
windowMs: 60 * 1000,
max: 10,
standardHeaders: true,
});
// Different limits per route
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Stricter for auth endpoints
skipSuccessfulRequests: true, // Don't count successful logins
});
app.post('/api/login', authLimiter, loginHandler);
// Custom key function (rate limit by user ID instead of IP)
const userLimiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
keyGenerator: (req) => req.user.id, // Requires auth middleware
});
Tiered Rate Limiting
// Different limits based on user tier
function getRateLimit(user) {
const tiers = {
free: { windowMs: 3600000, max: 100 }, // 100/hour
basic: { windowMs: 3600000, max: 1000 }, // 1000/hour
premium: { windowMs: 3600000, max: 10000 }, // 10k/hour
enterprise: { windowMs: 3600000, max: 100000 } // 100k/hour
};
return tiers[user.tier] || tiers.free;
}
app.use(async (req, res, next) => {
const user = await getUserFromToken(req);
const limits = getRateLimit(user);
const limiter = rateLimit({
...limits,
keyGenerator: () => user.id,
});
limiter(req, res, next);
});
Tools und Dienste
- Redis: Verteilte Zähler, automatisches Expire
- Kong: API Gateway mit Rate-Limiting-Plugin
- Nginx rate limiting: limit_req_zone, limit_conn_zone
- Cloudflare: Rate Limiting auf CDN-Ebene
- AWS API Gateway: Integriertes Throttling
- express-rate-limit: Express-Middleware
- Tyk: Open-Source-API-Gateway
Best Practices
- Wählen Sie den richtigen Algorithmus: Token Bucket für allgemeine APIs, Sliding Window für Genauigkeit
- Differenzierte Limits: Auth-Endpoints restriktiver, Read-Only permissiver
- Klare Kommunikation: Informative Header, hilfreiche Fehlermeldungen
- Whitelist: IPs vertrauenswürdiger Partner, Health Checks
- Monitoring: Alarmieren, wenn Nutzer häufig Limits erreichen
- Graceful Degradation: Nach Möglichkeit Cached Data zurückgeben
- Distributed State: Redis für Multi-Server-Deployments verwenden
- Cost-based Limiting: Teure Operationen verbrauchen mehr Tokens
Empfehlungen
Implementieren Sie für moderne APIs einen Token Bucket mit Redis für Distributed Rate Limiting. Verwenden Sie einen Sliding Window Counter, wenn Sie Genauigkeit ohne Speicher- Overhead benötigen. Konfigurieren Sie differenzierte Limits: 5 Req/min für Login, 100 Req/min für Lesezugriffe, 10 Req/min für Schreiboperationen. Geben Sie immer informative Header zurück und implementieren Sie eine Retry-Logik mit exponentiellem Backoff auf den Clients.
