Pub/sub
Zaris 2.0.0 adds native publish/subscribe messaging as a first-class client API (client.PubSub). A publisher sends a message to a named channel; every subscriber to that channel — connected to any node in the cluster — receives it. Delivery is push-based over the client's existing connection, the same mechanism Watch uses for change notifications.
Pub/sub also backs the Redis-protocol front-end: SUBSCRIBE, PSUBSCRIBE, and PUBLISH map onto this API, and Zaris's keyspace notifications are published through it. So a native .NET subscriber and a Redis client in another language can share the same channel.
The API
public interface IPubSubClient
{
Task<long> PublishAsync(string channel, byte[] message, CancellationToken ct = default);
Task<IPubSubSubscription> SubscribeAsync(
string[] channels, Func<ChannelMessage, Task> onMessage, CancellationToken ct = default);
Task<IPubSubSubscription> PSubscribeAsync(
string[] patterns, Func<ChannelMessage, Task> onMessage, CancellationToken ct = default);
}
public sealed class ChannelMessage
{
public string Channel { get; }
public string? Pattern { get; } // set when delivered via a pattern subscription
public byte[] Payload { get; }
}
A subscription is an IPubSubSubscription (an IAsyncDisposable); dispose it or call UnsubscribeAsync() to stop receiving.
Subscribe and publish
// Subscriber — the callback runs for each message on the channel
await using var sub = await client.PubSub.SubscribeAsync(
new[] { "orders" },
msg =>
{
Console.WriteLine($"{msg.Channel}: {Encoding.UTF8.GetString(msg.Payload)}");
return Task.CompletedTask;
});
// Publisher — returns the number of local subscribers the message reached
await client.PubSub.PublishAsync("orders", Encoding.UTF8.GetBytes("order 1001 placed"));
Pattern subscriptions match a channel glob, and ChannelMessage.Pattern tells you which pattern matched:
await using var sub = await client.PubSub.PSubscribeAsync(
new[] { "orders.*" },
msg => { /* msg.Pattern == "orders.*", msg.Channel == "orders.eu" */ return Task.CompletedTask; });
Delivery model
- Cluster-wide fan-out. A
PUBLISHon any node delivers to that node's local subscribers and broadcasts the message to every peer over the cluster bus; each peer delivers to its own local subscribers and does not re-broadcast. Net effect: a subscriber connected to any node receives a message published on any node, exactly once. - Fire-and-forget, at-most-once. Like Redis pub/sub, delivery is best-effort: there is no persistence, backlog, or replay. A subscriber that is not connected when a message is published does not receive it. The count returned by
PublishAsyncreflects local-node subscribers. If you need durable, replayable messaging with acknowledgements and consumer groups, use Streams instead. - Subscription lives on one node. A subscription is anchored to the node the client is connected to. If that node fails, re-subscribe after the client reconnects.
From Redis clients
Over the RESP front-end the standard commands work as expected:
| Redis command | Behaviour |
|---|---|
SUBSCRIBE ch [ch …] | Subscribe to one or more exact channels. |
PSUBSCRIBE pat [pat …] | Subscribe to channel patterns (glob). |
UNSUBSCRIBE / PUNSUBSCRIBE | Stop receiving. |
PUBLISH ch message | Publish; returns the receiver count (local node). |
Keyspace notifications (__keyspace@0__:<key> / __keyevent@0__:<event>) are delivered through the same native pub/sub, using the @0 database tag for compatibility.
See also
- Watch — push change notifications for a key or key prefix on the native client.
- Streams — durable, replayable messaging with consumer groups.
- Redis protocol (RESP) —
SUBSCRIBE/PUBLISHand keyspace notifications from any language.