NOTE
Der Redis-Client von Bun unterstützt Redis-Server-Versionen 7.2 und höher.Bun bietet native Bindungen für die Arbeit mit Redis-Datenbanken mit einer modernen, Promise-basierten API. Die Schnittstelle ist einfach und performant gestaltet, mit integriertem Verbindungsmanagement, vollständig typisierten Antworten und TLS-Unterstützung.
import { redis } from "bun";
// Einen Schlüssel setzen
await redis.set("greeting", "Hallo von Bun!");
// Einen Schlüssel abrufen
const greeting = await redis.get("greeting");
console.log(greeting); // "Hallo von Bun!"
// Einen Zähler inkrementieren
await redis.set("counter", 0);
await redis.incr("counter");
// Überprüfen, ob ein Schlüssel existiert
const exists = await redis.exists("greeting");
// Einen Schlüssel löschen
await redis.del("greeting");Erste Schritte
Um den Redis-Client zu verwenden, müssen Sie zunächst eine Verbindung erstellen:
import { redis, RedisClient } from "bun";
// Verwendung des Standard-Clients (liest Verbindungsinformationen aus der Umgebung)
// process.env.REDIS_URL wird standardmäßig verwendet
await redis.set("hello", "world");
const result = await redis.get("hello");
// Einen benutzerdefinierten Client erstellen
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");Standardmäßig liest der Client Verbindungsinformationen aus den folgenden Umgebungsvariablen (in Reihenfolge der Priorität):
REDIS_URLVALKEY_URL- Wenn nicht gesetzt, wird
"redis://localhost:6379"verwendet
Verbindungslebenszyklus
Der Redis-Client verwaltet Verbindungen automatisch im Hintergrund:
// Es wird keine Verbindung hergestellt, bis ein Befehl ausgeführt wird
const client = new RedisClient();
// Der erste Befehl initiiert die Verbindung
await client.set("key", "value");
// Die Verbindung bleibt für nachfolgende Befehle offen
await client.get("key");
// Die Verbindung explizit schließen, wenn fertig
client.close();Sie können den Verbindungslebenszyklus auch manuell steuern:
const client = new RedisClient();
// Explizit verbinden
await client.connect();
// Befehle ausführen
await client.set("key", "value");
// Trennen, wenn fertig
client.close();Grundlegende Operationen
String-Operationen
// Einen Schlüssel setzen
await redis.set("user:1:name", "Alice");
// Einen Schlüssel abrufen
const name = await redis.get("user:1:name");
// Einen Schlüssel als Uint8Array abrufen
const buffer = await redis.getBuffer("user:1:name");
// Einen Schlüssel löschen
await redis.del("user:1:name");
// Überprüfen, ob ein Schlüssel existiert
const exists = await redis.exists("user:1:name");
// Ablaufzeit setzen (in Sekunden)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // läuft in 1 Stunde ab
// Verbleibende Lebensdauer abrufen (in Sekunden)
const ttl = await redis.ttl("session:123");Numerische Operationen
// Anfangswert setzen
await redis.set("counter", "0");
// Um 1 inkrementieren
await redis.incr("counter");
// Um 1 dekrementieren
await redis.decr("counter");Hash-Operationen
// Mehrere Felder in einem Hash setzen
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);
// Mehrere Felder aus einem Hash abrufen
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]
// Ein einzelnes Feld aus Hash abrufen (gibt Wert direkt zurück, null wenn fehlend)
const userName = await redis.hget("user:123", "name");
console.log(userName); // "Alice"
// Ein numerisches Feld in einem Hash inkrementieren
await redis.hincrby("user:123", "visits", 1);
// Ein Float-Feld in einem Hash inkrementieren
await redis.hincrbyfloat("user:123", "score", 1.5);Set-Operationen
// Mitglied zu Set hinzufügen
await redis.sadd("tags", "javascript");
// Mitglied aus Set entfernen
await redis.srem("tags", "javascript");
// Überprüfen, ob Mitglied in Set existiert
const isMember = await redis.sismember("tags", "javascript");
// Alle Mitglieder eines Sets abrufen
const allTags = await redis.smembers("tags");
// Ein zufälliges Mitglied abrufen
const randomTag = await redis.srandmember("tags");
// Ein zufälliges Mitglied entfernen und zurückgeben
const poppedTag = await redis.spop("tags");Pub/Sub
Bun bietet native Bindungen für das Redis Pub/Sub Protokoll. Neu in Bun 1.2.23
Grundlegende Verwendung
Um mit dem Veröffentlichen von Nachrichten zu beginnen, können Sie einen Publisher in publisher.ts einrichten:
import { RedisClient } from "bun";
const writer = new RedisClient("redis://localhost:6739");
await writer.connect();
writer.publish("general", "Hallo zusammen!");
writer.close();Erstellen Sie in einer anderen Datei den Subscriber in subscriber.ts:
import { RedisClient } from "bun";
const listener = new RedisClient("redis://localhost:6739");
await listener.connect();
await listener.subscribe("general", (message, channel) => {
console.log(`Empfangen: ${message}`);
});Führen Sie in einer Shell Ihren Subscriber aus:
bun run subscriber.tsund in einer anderen Ihren Publisher:
bun run publisher.tsNOTE
Der Abonnement-Modus übernimmt die `RedisClient`-Verbindung. Ein Client mit Abonnements kann nur `RedisClient.prototype.subscribe()` aufrufen. Mit anderen Worten, Anwendungen, die mit Redis kommunizieren müssen, benötigen eine separate Verbindung, erhältlich über `.duplicate()`:import { RedisClient } from "bun";
const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
const subscriber = await redis.duplicate();
await subscriber.subscribe("foo", () => {});
await redis.set("bar", "baz");Veröffentlichen
Das Veröffentlichen von Nachrichten erfolgt über die publish()-Methode:
await client.publish(channelName, message);Abonnements
Der Bun RedisClient ermöglicht es Ihnen, Kanäle über die .subscribe()-Methode zu abonnieren:
await client.subscribe(channel, (message, channel) => {});Sie können über die .unsubscribe()-Methode kündigen:
await client.unsubscribe(); // Von allen Kanälen kündigen.
await client.unsubscribe(channel); // Von einem bestimmten Kanal kündigen.
await client.unsubscribe(channel, listener); // Einen bestimmten Listener kündigen.Erweiterte Verwendung
Befehlsausführung und Pipelining
Der Client pipeliniert Befehle automatisch und verbessert die Leistung, indem er mehrere Befehle in einem Batch sendet und Antworten verarbeitet, sobald sie eintreffen.
// Befehle werden standardmäßig automatisch gepipelined
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);Um das automatische Pipelining zu deaktivieren, können Sie die enableAutoPipelining-Option auf false setzen:
const client = new RedisClient("redis://localhost:6379", {
enableAutoPipelining: false,
});Rohe Befehle
Wenn Sie Befehle verwenden müssen, die keine Komfortmethoden haben, können Sie die send-Methode verwenden:
// Einen beliebigen Redis-Befehl ausführen
const info = await redis.send("INFO", []);
// LPUSH zu einer Liste
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
// Listenbereich abrufen
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);Die send-Methode ermöglicht es Ihnen, jeden Redis-Befehl zu verwenden, auch solche, die keine dedizierten Methoden im Client haben. Das erste Argument ist der Befehlsname und das zweite Argument ist ein Array von String-Argumenten.
Verbindungsereignisse
Sie können Handler für Verbindungsereignisse registrieren:
const client = new RedisClient();
// Wird aufgerufen, wenn erfolgreich mit dem Redis-Server verbunden
client.onconnect = () => {
console.log("Mit Redis-Server verbunden");
};
// Wird aufgerufen, wenn die Verbindung zum Redis-Server getrennt wird
client.onclose = error => {
console.error("Verbindung zum Redis-Server getrennt:", error);
};
// Manuell verbinden/trennen
await client.connect();
client.close();Verbindungsstatus und Überwachung
// Überprüfen, ob verbunden
console.log(client.connected); // boolean, der den Verbindungsstatus angibt
// Menge der gepufferten Daten überprüfen (in Bytes)
console.log(client.bufferedAmount);Typkonvertierung
Der Redis-Client handles automatische Typkonvertierung für Redis-Antworten:
- Integer-Antworten werden als JavaScript-Zahlen zurückgegeben
- Bulk-Strings werden als JavaScript-Strings zurückgegeben
- Einfache Strings werden als JavaScript-Strings zurückgegeben
- Null-Bulk-Strings werden als
nullzurückgegeben - Array-Antworten werden als JavaScript-Arrays zurückgegeben
- Fehlerantworten werfen JavaScript-Fehler mit entsprechenden Fehlercodes
- Boolean-Antworten (RESP3) werden als JavaScript-Booleans zurückgegeben
- Map-Antworten (RESP3) werden als JavaScript-Objekte zurückgegeben
- Set-Antworten (RESP3) werden als JavaScript-Arrays zurückgegeben
Spezielle Behandlung für bestimmte Befehle:
EXISTSgibt einen Boolean statt einer Zahl zurück (1 wird true, 0 wird false)SISMEMBERgibt einen Boolean zurück (1 wird true, 0 wird false)
Die folgenden Befehle deaktivieren das automatische Pipelining:
AUTHINFOQUITEXECMULTIWATCHSCRIPTSELECTCLUSTERDISCARDUNWATCHPIPELINESUBSCRIBEUNSUBSCRIBEUNPSUBSCRIBE
Verbindungsoptionen
Beim Erstellen eines Clients können Sie verschiedene Optionen übergeben, um die Verbindung zu konfigurieren:
const client = new RedisClient("redis://localhost:6379", {
// Verbindungs-Timeout in Millisekunden (Standard: 10000)
connectionTimeout: 5000,
// Leerlauf-Timeout in Millisekunden (Standard: 0 = kein Timeout)
idleTimeout: 30000,
// Ob bei Trennung automatisch neu verbunden wird (Standard: true)
autoReconnect: true,
// Maximale Anzahl von Wiederherstellungsversuchen (Standard: 10)
maxRetries: 10,
// Ob Befehle bei Trennung in die Warteschlange gestellt werden (Standard: true)
enableOfflineQueue: true,
// Ob Befehle automatisch gepipelined werden (Standard: true)
enableAutoPipelining: true,
// TLS-Optionen (Standard: false)
tls: true,
// Alternativ benutzerdefinierte TLS-Konfiguration angeben:
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// cert: "path/to/cert.pem",
// key: "path/to/key.pem",
// }
});Wiederherstellungsverhalten
Wenn eine Verbindung verloren geht, versucht der Client automatisch, mit exponentiellem Backoff neu zu verbinden:
- Der Client beginnt mit einer kleinen Verzögerung (50ms) und verdoppelt sie bei jedem Versuch
- Die Wiederherstellungsverzögerung ist auf 2000ms (2 Sekunden) begrenzt
- Der Client versucht bis zu
maxRetries-Mal neu zu verbinden (Standard: 10) - Befehle, die während der Trennung ausgeführt werden, werden:
- In die Warteschlange gestellt, wenn
enableOfflineQueuetrue ist (Standard) - Sofort abgelehnt, wenn
enableOfflineQueuefalse ist
- In die Warteschlange gestellt, wenn
Unterstützte URL-Formate
Der Redis-Client unterstützt verschiedene URL-Formate:
// Standard-Redis-URL
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");
// Mit Authentifizierung
new RedisClient("redis://username:password@localhost:6379");
// Mit Datenbanknummer
new RedisClient("redis://localhost:6379/0");
// TLS-Verbindungen
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
// Unix-Socket-Verbindungen
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");
// TLS über Unix-Socket
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");Fehlerbehandlung
Der Redis-Client wirft typisierte Fehler für verschiedene Szenarien:
try {
await redis.get("non-existent-key");
} catch (error) {
if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
console.error("Verbindung zum Redis-Server wurde geschlossen");
} else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
console.error("Authentifizierung fehlgeschlagen");
} else {
console.error("Unerwarteter Fehler:", error);
}
}Häufige Fehlercodes:
ERR_REDIS_CONNECTION_CLOSED- Verbindung zum Server wurde geschlossenERR_REDIS_AUTHENTICATION_FAILED- Authentifizierung beim Server fehlgeschlagenERR_REDIS_INVALID_RESPONSE- Ungültige Antwort vom Server erhalten
Beispiel-Anwendungsfälle
Caching
async function getUserWithCache(userId) {
const cacheKey = `user:${userId}`;
// Zuerst versuchen, aus dem Cache abzurufen
const cachedUser = await redis.get(cacheKey);
if (cachedUser) {
return JSON.parse(cachedUser);
}
// Nicht im Cache, aus Datenbank abrufen
const user = await database.getUser(userId);
// Für 1 Stunde im Cache speichern
await redis.set(cacheKey, JSON.stringify(user));
await redis.expire(cacheKey, 3600);
return user;
}Ratenbegrenzung
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
const key = `ratelimit:${ip}`;
// Zähler inkrementieren
const count = await redis.incr(key);
// Ablaufzeit setzen, wenn dies die erste Anfrage im Fenster ist
if (count === 1) {
await redis.expire(key, windowSecs);
}
// Überprüfen, ob Limit überschritten wurde
return {
limited: count > limit,
remaining: Math.max(0, limit - count),
};
}Session-Speicher
async function createSession(userId, data) {
const sessionId = crypto.randomUUID();
const key = `session:${sessionId}`;
// Session mit Ablaufzeit speichern
await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
await redis.expire(key, 86400); // 24 Stunden
return sessionId;
}
async function getSession(sessionId) {
const key = `session:${sessionId}`;
// Session-Daten abrufen
const exists = await redis.exists(key);
if (!exists) return null;
const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);
return {
userId: Number(userId),
created: Number(created),
data: JSON.parse(data),
};
}Implementierungshinweise
Buns Redis-Client ist in Zig implementiert und verwendet das Redis Serialization Protocol (RESP3). Er verwaltet Verbindungen effizient und bietet automatische Wiederherstellung mit exponentiellem Backoff.
Der Client unterstützt das Pipelining von Befehlen, was bedeutet, dass mehrere Befehle gesendet werden können, ohne auf die Antworten vorheriger Befehle zu warten. Dies verbessert die Leistung erheblich, wenn mehrere Befehle hintereinander gesendet werden.
Einschränkungen und Zukunftspläne
Aktuelle Einschränkungen des Redis-Clients, die wir in zukünftigen Versionen beheben möchten:
- Transaktionen (MULTI/EXEC) müssen vorerst über rohe Befehle durchgeführt werden
Nicht unterstützte Funktionen:
- Redis Sentinel
- Redis Cluster