Skip to content

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.

ts
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:

ts
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_URL
  • VALKEY_URL
  • Wenn nicht gesetzt, wird "redis://localhost:6379" verwendet

Verbindungslebenszyklus

Der Redis-Client verwaltet Verbindungen automatisch im Hintergrund:

ts
// 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:

ts
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

ts
// 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

ts
// Anfangswert setzen
await redis.set("counter", "0");

// Um 1 inkrementieren
await redis.incr("counter");

// Um 1 dekrementieren
await redis.decr("counter");

Hash-Operationen

ts
// 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

ts
// 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:

typescript
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:

typescript
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:

bash
bun run subscriber.ts

und in einer anderen Ihren Publisher:

bash
bun run publisher.ts

NOTE

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()`:
ts
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:

typescript
await client.publish(channelName, message);

Abonnements

Der Bun RedisClient ermöglicht es Ihnen, Kanäle über die .subscribe()-Methode zu abonnieren:

typescript
await client.subscribe(channel, (message, channel) => {});

Sie können über die .unsubscribe()-Methode kündigen:

typescript
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.

ts
// 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:

ts
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:

ts
// 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:

ts
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

ts
// Ü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 null zurü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:

  • EXISTS gibt einen Boolean statt einer Zahl zurück (1 wird true, 0 wird false)
  • SISMEMBER gibt einen Boolean zurück (1 wird true, 0 wird false)

Die folgenden Befehle deaktivieren das automatische Pipelining:

  • AUTH
  • INFO
  • QUIT
  • EXEC
  • MULTI
  • WATCH
  • SCRIPT
  • SELECT
  • CLUSTER
  • DISCARD
  • UNWATCH
  • PIPELINE
  • SUBSCRIBE
  • UNSUBSCRIBE
  • UNPSUBSCRIBE

Verbindungsoptionen

Beim Erstellen eines Clients können Sie verschiedene Optionen übergeben, um die Verbindung zu konfigurieren:

ts
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:

  1. Der Client beginnt mit einer kleinen Verzögerung (50ms) und verdoppelt sie bei jedem Versuch
  2. Die Wiederherstellungsverzögerung ist auf 2000ms (2 Sekunden) begrenzt
  3. Der Client versucht bis zu maxRetries-Mal neu zu verbinden (Standard: 10)
  4. Befehle, die während der Trennung ausgeführt werden, werden:
    • In die Warteschlange gestellt, wenn enableOfflineQueue true ist (Standard)
    • Sofort abgelehnt, wenn enableOfflineQueue false ist

Unterstützte URL-Formate

Der Redis-Client unterstützt verschiedene URL-Formate:

ts
// 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:

ts
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 geschlossen
  • ERR_REDIS_AUTHENTICATION_FAILED - Authentifizierung beim Server fehlgeschlagen
  • ERR_REDIS_INVALID_RESPONSE - Ungültige Antwort vom Server erhalten

Beispiel-Anwendungsfälle

Caching

ts
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

ts
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

ts
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

Bun von www.bunjs.com.cn bearbeitet