Redis jenseits des Cachings: Datenstrukturen, Pub/Sub und Echtzeit-Anwendungen
Lerne, wie du Redis als mehr als nur einen Cache einsetzt – entdecke Datenstrukturen, Pub/Sub-Messaging, Streams und Muster für den Aufbau schneller, skalierbarer Anwendungen.
Die meisten Entwickler kennen Redis als „diesen schnellen Cache". Doch Redis ist ein vollwertiger In-Memory-Datenstrukturserver, der Session-Management, Echtzeit-Bestenlisten, Rate-Limiting, Message-Queues und vieles mehr ermöglicht – alles mit Sub-Millisekunden-Latenz.
Warum Redis so schnell ist
Redis speichert alles im Arbeitsspeicher und verwendet einen Single-Threaded Event Loop zur Verarbeitung von Befehlen. Kein Disk-I/O bei Lesevorgängen, kein Lock-Contention, kein Context-Switching. Das Ergebnis: 100.000+ Operationen pro Sekunde auf handelsüblicher Hardware.
Typischer Latenzvergleich:
┌─────────────────┬──────────────┐
│ Operation │ Latenz │
├─────────────────┼──────────────┤
│ Redis GET │ 0,1 ms │
│ PostgreSQL SEL │ 1-5 ms │
│ REST API call │ 50-200 ms │
│ Disk read │ 5-10 ms │
└─────────────────┴──────────────┘
Kerndatenstrukturen
Redis ist nicht nur ein einfacher Key-Value-Speicher. Es unterstützt umfangreiche Datenstrukturen, die direkt auf häufige Anwendungsanforderungen zugeschnitten sind.
Strings – der Grundbaustein
Strings speichern Text, Zahlen oder Binärdaten bis zu 512 MB:
SET user:1001:name "Alice"
GET user:1001:name # "Alice"
# Atomarer Zähler
INCR page:views # 1, 2, 3, ...
INCRBY cart:total 2500 # 25,00 hinzufügen (in Cent gespeichert)
# Ablaufende Keys (TTL)
SET session:abc123 "{...}" EX 3600 # Läuft in 1 Stunde ab
TTL session:abc123 # Verbleibende Sekunden
Hashes – leichtgewichtige Objekte
Hashes eignen sich hervorragend zum Speichern von Objekten ohne Serialisierungs-Overhead:
HSET user:1001 name "Alice" email "alice@example.com" plan "pro"
HGET user:1001 name # "Alice"
HGETALL user:1001 # Alle Felder und Werte
HINCRBY user:1001 logins 1 # Atomares Feld-Inkrement
Hashes verbrauchen 10-mal weniger Speicher als das Speichern jedes Feldes als separaten Key.
Lists – Queues und Feeds
Geordnete Sammlungen, die Push/Pop von beiden Seiten unterstützen:
LPUSH notifications:alice "Neuer Kommentar zu deinem Beitrag"
LPUSH notifications:alice "Du hast einen neuen Follower"
LRANGE notifications:alice 0 9 # Neueste 10 Benachrichtigungen
# Als Queue verwenden (Producer/Consumer)
RPUSH queue:emails "send-welcome"
LPOP queue:emails # Nächsten Job verarbeiten
Sets – eindeutige Sammlungen
Ungeordnete Sammlungen einzigartiger Strings:
SADD tags:post:42 "redis" "database" "nosql"
SMEMBERS tags:post:42 # Alle Tags
SISMEMBER tags:post:42 "redis" # true
# Set-Operationen
SINTER tags:post:42 tags:post:99 # Gemeinsame Tags
SUNION tags:post:42 tags:post:99 # Alle Tags zusammen
Sorted Sets – Bestenlisten und Rankings
Sets mit einem Score für jedes Element, automatisch sortiert:
ZADD leaderboard 1500 "alice" 2300 "bob" 1800 "charlie"
ZREVRANGE leaderboard 0 2 WITHSCORES # Top-3-Spieler
ZRANK leaderboard "alice" # Rang (0-basiert)
ZINCRBY leaderboard 100 "alice" # Alice erzielt 100 Punkte
So werden Spiel-Bestenlisten, Trending-Beiträge und Priority-Queues in großem Maßstab realisiert.
Praxisnahe Muster
Muster 1: Session-Speicherung
Statt datenbankgestützter Sessions verwendet man Redis für sofortige Abfragen:
import redis
r = redis.Redis()
# Session speichern
r.setex(f"session:{token}", 3600, json.dumps(user_data))
# Session abrufen
data = r.get(f"session:{token}")
Warum? Datenbank-Sessions fügen 1–5 ms pro Anfrage hinzu. Redis-Sessions fügen 0,1 ms hinzu. Bei 1.000 Anfragen/s spart das 5 Sekunden pro Sekunde.
Muster 2: Rate-Limiting
Sliding-Window-Rate-Limiter mit 3 Befehlen:
# 100 Anfragen pro Minute pro Nutzer erlauben
MULTI
INCR rate:user:1001
EXPIRE rate:user:1001 60
EXEC
# Prüfen: wenn INCR-Ergebnis > 100, Anfrage ablehnen
Muster 3: Caching mit Cache-Aside
Das gängigste Caching-Muster:
def get_user(user_id):
# 1. Cache prüfen
cached = redis.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# 2. Cache-Miss → Datenbank abfragen
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
# 3. Cache befüllen (läuft in 5 Minuten ab)
redis.setex(f"user:{user_id}", 300, json.dumps(user))
return user
Muster 4: Pub/Sub für Echtzeit-Events
Redis Pub/Sub ermöglicht Echtzeit-Messaging zwischen Services:
# Subscriber (lauscht auf Nachrichten)
SUBSCRIBE chat:room:42
# Publisher (sendet Nachrichten)
PUBLISH chat:room:42 "Hallo alle!"
Im Anwendungscode:
# Publisher
redis.publish("notifications", json.dumps({
"type": "new_order",
"order_id": 12345
}))
# Subscriber
pubsub = redis.pubsub()
pubsub.subscribe("notifications")
for message in pubsub.listen():
handle_notification(message)
Muster 5: Distributed Locks
Race-Conditions über mehrere Server hinweg verhindern:
# Lock erwerben (NX = nur wenn nicht vorhanden, EX = ablaufen)
SET lock:invoice:gen "worker-1" NX EX 30
# Lock freigeben (nur wenn man Eigentümer ist — Lua-Script verwenden)
Redis Streams – Event-Sourcing und Message-Queues
Streams sind Redis' Antwort auf Kafka-artige Event-Logs:
# Events hinzufügen
XADD orders * user_id 1001 product "Widget" qty 3
XADD orders * user_id 1002 product "Gadget" qty 1
# Neueste Events lesen
XRANGE orders - + COUNT 10
# Consumer-Groups (mehrere Worker)
XGROUP CREATE orders processors $ MKSTREAM
XREADGROUP GROUP processors worker-1 COUNT 5 BLOCK 2000 STREAMS orders >
Streams bieten persistente, wiedergebbare Event-Logs mit Consumer-Groups – ideal für Microservices-Kommunikation.
Performance-Tipps
1. Pipelining für Massenoperationen nutzen
Statt 100 Round-Trips 100 Befehle auf einmal senden:
pipe = redis.pipeline()
for i in range(100):
pipe.set(f"key:{i}", f"value:{i}")
pipe.execute() # Einzelner Round-Trip
2. Die richtige Datenstruktur wählen
- Zähler benötigt? →
INCR(nicht GET + SET) - Objekt benötigt? → Hash (nicht serialisierter JSON-String)
- Einmalige Elemente benötigt? → Set (nicht List + Dedup-Logik)
- Ranking benötigt? → Sorted Set (nicht Sortierung nach Abfrage)
3. TTL auf alles setzen
Der Arbeitsspeicher ist begrenzt. Immer Ablaufzeiten für Cache-Keys setzen:
SET cache:api:result "{...}" EX 300 # 5-Minuten-TTL
4. Key-Benennungskonventionen verwenden
ressource:id:feld
user:1001:profile
cache:api:v2:products
session:abc123
queue:emails:pending
Persistenz: RDB vs. AOF
Redis kann Daten zur Datensicherheit auf die Festplatte schreiben:
| Modus | Funktionsweise | Kompromiss |
|---|---|---|
| RDB | Point-in-Time-Snapshots | Schnelle Wiederherstellung, möglicher Datenverlust |
| AOF | Protokolliert jeden Schreibvorgang | Langsamer, minimaler Datenverlust |
| Beide | RDB + AOF kombiniert | Beste Datensicherheit |
# In redis.conf
save 900 1 # Snapshot, wenn sich 1 Key in 900 s geändert hat
appendonly yes # AOF aktivieren
appendfsync everysec # Fsync jede Sekunde
Kurzreferenz für Befehle
Du möchtest einen bestimmten Redis-Befehl nachschlagen? Nutze unser Redis Cheat Sheet – es umfasst 200+ Befehle, nach Datenstruktur geordnet, mit Syntax, Beispielen und Einzel-Klick-Kopieren.
Fazit
Redis ist weit mehr als ein Cache. Seine Datenstrukturen passen perfekt zu echten Anwendungsanforderungen:
- Strings → Sessions, Zähler, einfacher Cache
- Hashes → Nutzerprofile, Konfigurationsobjekte
- Lists → Queues, Aktivitäts-Feeds, zuletzt aufgerufene Elemente
- Sets → Tags, eindeutige Besucher, Beziehungen
- Sorted Sets → Bestenlisten, Rankings, Priority-Queues
- Streams → Event-Sourcing, Message-Queues
- Pub/Sub → Echtzeit-Benachrichtigungen, Chat
Starte mit Caching, aber hör nicht dort auf. Redis kann mehrere Infrastrukturkomponenten in deinem Stack ersetzen – und das alles mit Sub-Millisekunden-Geschwindigkeit.