Skip to main content

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 PUBLISH on 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 PublishAsync reflects 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 commandBehaviour
SUBSCRIBE ch [ch …]Subscribe to one or more exact channels.
PSUBSCRIBE pat [pat …]Subscribe to channel patterns (glob).
UNSUBSCRIBE / PUNSUBSCRIBEStop receiving.
PUBLISH ch messagePublish; 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/PUBLISH and keyspace notifications from any language.