Keyspace-Benachrichtigungen, die ein Failover überstehen
Keyspace-Benachrichtigungen sind eines der nützlichsten Features von Redis und eines der unauffällig fragilsten. Die Idee ist wunderbar: Jedes Mal, wenn ein Schlüssel geschrieben, gelöscht oder ablaufend ist, veröffentlicht der Server ein Event auf einem wohlbekannten Pub/Sub-Kanal, und jeder Interessierte kann per PSUBSCRIBE reagieren — einen Cache invalidieren, einen Worker wecken, eine Projektion aktualisieren. Kein Polling, kein GET in einer Schleife, das fragt „hat es sich schon geändert?".
Die Fragilität steckt im Kleingedruckten. Redis-Keyspace-Benachrichtigungen sind Fire-and-Forget-Pub/Sub, ausgesendet von dem Node, der den Befehl verarbeitet hat, an Subscriber, die mit genau diesem Node verbunden sind. In einem geclusterten Deployment heißt das: Ein Subscriber hört nur von Schreibvorgängen, die zufällig auf dem Node landeten, an den er angebunden ist, und fällt dieser Node per Failover aus, geht die Subscription — und der Event-Strom, den sie trug — mit. Das Feature funktioniert auf einer einzelnen Box wunderbar und wird schnell subtil, sobald es mehr als eine gibt.
Zaris spricht exakt dasselbe Protokoll — CONFIG SET notify-keyspace-events, die Kanäle __keyspace@0__ und __keyevent@0__, ein gewöhnliches PSUBSCRIBE von jedem Redis-Client in jeder Sprache. Die Mechanik darunter ist jedoch keine Neuimplementierung des Redis-Notifiers. Es ist eine dünne Brücke auf ein Feature, das Zaris bereits hatte: einen nativen, clusterweiten, replizierten Watch. Das ändert, was die Benachrichtigungen wert sind.
Einschalten — exakt auf Redis-Art
Am Aktivieren und Konsumieren des Features ist nichts Zaris-spezifisch. Richten Sie einen beliebigen Redis-Client auf das RESP-Frontend und verwenden Sie die Befehle, die Sie bereits kennen:
# Enable notifications for keyspace (K) and keyevent (E) channels
redis-cli -p 6380 CONFIG SET notify-keyspace-events KEA
# Subscribe to every key-event, cluster-wide
redis-cli -p 6380 PSUBSCRIBE '__keyevent@0__:*'
1) "pmessage"
2) "__keyevent@0__:*"
3) "__keyevent@0__:set"
4) "user:42"
Von einem typisierten Client — hier StackExchange.Redis — sieht es genauso aus, ganz ohne Zaris-SDK:
var mux = await ConnectionMultiplexer.ConnectAsync("localhost:6380");
var sub = mux.GetSubscriber();
await sub.SubscribeAsync(
new RedisChannel("__keyevent@0__:expired", RedisChannel.PatternMode.Literal),
(channel, key) => Console.WriteLine($"key expired: {key}"));
Der Punkt, den es zu unterstreichen lohnt: keine Anwendungsänderung. Code, der gegen Redis-Keyspace-Benachrichtigungen geschrieben wurde, läuft unverändert gegen Zaris. Das ist das gesamte Versprechen des RESP-Frontends — Ihre Clients müssen nicht wissen, was hinter dem Socket steckt.
Die zwei Kanalfamilien
Zaris veröffentlicht die zwei Standard-Redis-Kanalfamilien mit der Standard-Payload-Konvention — welche Sie abonnieren, hängt davon ab, ob Sie nach dem Schlüssel oder nach dem Event gehen:
| Kanal | Veröffentlicht, wenn Flag … | Payload | Verwenden, wenn Sie sich interessieren für … |
|---|---|---|---|
__keyspace@0__:<key> | K gesetzt ist | der Event-Name (set / del / expired) | einen bestimmten Schlüssel — „sag mir alles, was mit session:abc passiert" |
__keyevent@0__:<event> | E gesetzt ist | der Schlüssel, der sich geändert hat | eine Klasse von Events — „sag mir jeden Schlüssel, der gerade abgelaufen ist" |
Das @0 ist der Datenbankindex und in Zaris immer 0 — Zaris teilt den Keyspace nicht in nummerierte Datenbanken auf, es gibt also einen einzigen, flachen Benachrichtigungs-Namensraum statt sechzehn. Die drei Event-Namen bilden die drei Dinge ab, die einem Schlüssel passieren können:
set— der Schlüssel wurde geschrieben (einSET, einHSET, einLPUSH, jede Mutation)del— der Schlüssel wurde entferntexpired— der Schlüssel wurde per Eviction entfernt, weil sein TTL abgelaufen ist
Was darunter tatsächlich geschieht
Senden Sie CONFIG SET notify-keyspace-events KEA, schaltet Zaris keinen eigens gebauten Redis-Event-Emitter im Befehlspfad frei. Es registriert einen einzigen Prefix-Watch über den gesamten Keyspace auf dem eigenen Routing-Client des Nodes — dasselbe Watch-Primitiv, das der native .NET-Client für reaktive Lesevorgänge ohne Polling bereitstellt. Ab da ist die einzige Logik in der RESP-Schicht eine Namenszuordnung: Ein nativer Watch-Event trifft ein, und die Brücke macht daraus den richtigen Redis-Kanalnamen und die richtige Payload.
// The entire RESP-side translation, essentially verbatim:
var eventName = e.EventType switch
{
WatchEventType.Put => "set",
WatchEventType.Delete => "del",
WatchEventType.Evicted => "expired",
_ => null, // snapshots / heartbeats carry no keyspace meaning
};
if (keyspace) PubSub.Publish($"__keyspace@0__:{e.Key}", eventName);
if (keyevent) PubSub.Publish($"__keyevent@0__:{eventName}", e.Key);
Dieser Publish-Aufruf ist das native Pub/Sub — dieselbe Subscribe-/Deliver-Mechanik mit eigenem clusterweitem Fan-out, die der Store ohnehin betreibt. Das RESP-Frontend besitzt das Vokabular (Kanalnamen, Payload-Konvention); der Cluster besitzt die Zustellung. Dieses Muster halten wir im gesamten Produkt bewusst durch: Jedes Feature ist zuerst eine native Fähigkeit, und RESP ist eine dünne Haut darüber — nie eine parallele Implementierung mit eigenen Bugs.
Warum „der Watch ist nativ" der ganze Punkt ist
Hier trennen sich die beiden Systeme, und das ist nicht kosmetisch.
Eine Redis-Keyspace-Benachrichtigung wird auf dem Node erzeugt, der den Befehl ausgeführt hat, und an Subscriber zugestellt, die mit diesem Node verbunden sind. Nichts an der Subscription wird mit dem Rest des Clusters geteilt, und nichts daran ist dauerhaft. Es ist Pub/Sub im strengen Sinne — waren Sie im richtigen Moment nicht auf dem richtigen Node am Lauschen, existierte das Event für Sie schlicht nicht.
Eine Zaris-Keyspace-Benachrichtigung fährt auf einem nativen Watch, und ein nativer Watch ist ein clusterweites Objekt:
- Die Subscription wird über die Mitglieder repliziert.
WatchSyncServicehält Watch-Subscriptions zwischen Nodes synchron — der Leader schiebt die aktuelle Menge an einen beitretenden Node, und ein hochkommender Node fragt den Leader, was aktiv ist. Die Subscription ist keine Tatsache, die nur ein Prozess kennt; sie ist Cluster-Zustand. - Sie wird bei Churn neu verortet. Wenn Partitionen wandern — Failover, Migration, ein Node verlässt den Cluster —, wandert der Watch mit den Daten, die er beobachtet, so wie der Schlüsselbesitz. Der Schreibvorgang erzeugt auch nachdem sich der Staub gelegt hat ein Event, weil der Watcher der Partition gefolgt ist.
- Die Zustellung verteilt sich clusterweit. Weil der Publish über natives Pub/Sub läuft, erhält ein mit irgendeinem Node verbundener Client Events für Schreibvorgänge auf irgendeinem Node. Sie sind nicht stillschweigend nur auf die Aktivität eines Shards abonniert.
Das praktische Ergebnis: Ein Subscriber, der __keyevent@0__:expired beobachtet, um eine Cache-Invalidierung zu steuern, funktioniert durch einen Node-Ausfall hindurch weiter. Im Redis Cluster hängt derselbe Subscriber an einem Node und sieht die Ablaufereignisse eines Nodes; die ehrliche Abhilfe dort ist, auf jedem Node eine Subscription zu öffnen und zu deduplizieren. Auf Zaris ist es eine Subscription, clustervollständig, und sie übersteht das Failover, das sie stillschweigend gekappt hätte.
Die ehrlichen Grenzen
Dies ist eine Brücke über Best-Effort-Pub/Sub, kein magisches Zuverlässigkeits-Upgrade. Drei Dinge sollten Sie auseinanderhalten:
Die Zustellung ist weiterhin Best-Effort, kein garantiertes Log. Die Brücke veröffentlicht jedes Event Fire-and-Forget: Ein Zustellfehler bei einem Subscriber darf die Watch-Pipeline für alle anderen nie blockieren, daher wird eine Benachrichtigung an einen kurzzeitig getrennten Client verworfen, nicht gepuffert. Das entspricht dem eigenen Vertrag von Redis — Keyspace-Benachrichtigungen waren nie ein Exactly-once-Event-Log, und Sie sollten die von Zaris auch nicht als eines behandeln. Würde ein verpasstes Event Ihren Zustand beschädigen, sind Benachrichtigungen das Aufwecken, nicht die Quelle der Wahrheit: Lesen Sie beim Event den maßgeblichen Wert.
Kein Backfill. Der Watch wird ohne initialen Snapshot scharf gestellt, Sie erhalten also Änderungen ab dem Moment, ab dem Benachrichtigungen aktiviert sind — nie einen Replay dessen, was der Keyspace bereits enthielt. Aktivieren Sie die Benachrichtigungen, dann denken Sie über Deltas nach.
Die Flag-Granularität ist grob. Redis lässt Sie Event-Klassen mit einem dichten Flag-String auswählen (g, $, l, s, h, z, x, e, …). Zaris liest die zwei Flags, die entscheiden, wohin veröffentlicht wird — K für die Keyspace-Kanäle, E für die Keyevent-Kanäle (und A als die übliche Kurzform für „alle Klassen") — und überbrückt die drei nativen Event-Typen, sobald eines gesetzt ist. Es filtert nicht auf Subscriptions pro Klasse herunter. In der Praxis abonnieren Sie die Kanäle und Muster, die Sie interessieren, und lassen den Musterabgleich die Eingrenzung erledigen — so ist ohnehin der meiste Keyspace-Benachrichtigungs-Code geschrieben.
Brauchen Sie Benachrichtigungen für einen Teil des Keyspace statt für alles, setzen Sie ZARIS_RESP_KEYSPACE_PREFIX auf dem Node, und der zugrunde liegende Watch wird auf diesem Präfix statt auf dem gesamten Store scharf gestellt — ein billiger Weg, einen geschäftigen Keyspace davon abzuhalten, Events zu veröffentlichen, die niemand abonniert.
Wenn Sie ein Log statt einer Türklingel wollen
Keyspace-Benachrichtigungen sind eine Türklingel: Sie sagen Ihnen, dass etwas geschehen ist, genau jetzt, wem auch immer zuhört. Brauchen Sie dagegen ein wiederabspielbares, dauerhaftes Protokoll — jedes Event, in Reihenfolge, lesbar für einen Consumer, der spät dazukam oder abstürzte und zurückkehrte —, ist das keine Benachrichtigung, sondern ein Stream. Zaris hat dafür native Redis Streams: XADD zum Anhängen, XREADGROUP mit Consumer Groups für At-least-once-Verarbeitung mit Bestätigungen, blockierende Lesevorgänge für niedrige Latenz. Greifen Sie zu Benachrichtigungen, wenn ein verpasstes Event nur „nächstes Mal wieder nachsehen" bedeutet; greifen Sie zu Streams, wenn ein verpasstes Event ein Bug ist.
Verwendet für das, was sie ist — ein latenzarmes Aufwecken ohne Polling, das auf Zaris zufällig clustervollständig und Failover-dauerhaft ist —, gehört die Keyspace-Benachrichtigung zu den schönsten Dingen, die Sie mit einem einzigen CONFIG SET einschalten können. Der Unterschied zeigt sich erst an dem Tag, an dem ein Node verschwindet, also genau an dem Tag, an dem Sie die Grenzen der Ein-Node-Version auf die harte Tour entdeckt hätten.