idatabase.publish 无法实现真正的发布订阅,因为它仅发送消息而不监听或维持连接;必须用 isubscriber 启动长连接并注册持久回调,且需在应用启动时初始化,避免局部匿名函数捕获已释放对象。

为什么直接用 IDatabase.Publish 无法实现真正的发布订阅
因为 IDatabase.Publish 只是向 Redis 的 PUBSUB 通道发一条消息,它不负责监听、不维持连接、也不自动处理订阅端逻辑。真正实现“发布-订阅”需要两个独立角色:一个用 ISubscriber 订阅并接收消息,另一个用 IDatabase 或 ISubscriber 发布。很多人误以为调一次 Publish 就完事,结果发现收不到回调——问题出在没启动订阅连接。
必须用 ISubscriber 启动长连接监听
ISubscriber 是 StackExchange.Redis 中唯一支持持续监听 PUBSUB 的接口,它底层复用 ConnectionMultiplexer 的物理连接,但会独占一个 socket 用于接收 SUBSCRIBE 响应。一旦断开或未正确注册回调,消息就丢了。
- 订阅必须在连接建立后、且在
ConnectionMultiplexer.GetSubscriber()返回实例后立即调用Subscribe,不能延迟到事件触发时才调 - 回调函数(
Action<redischannel redisvalue></redischannel>)必须是长期存活的委托,不能是局部匿名函数+捕获已释放对象(比如在 Web API 的 Action 内直接sub.Subscribe(...)) - 推荐在应用启动时(如 .NET 8 的
Program.cs)初始化并持久持有ISubscriber实例,避免频繁创建销毁
Subscribe 和 SubscribeAsync 的区别与陷阱
两者都返回 IPublication,但行为不同:Subscribe 是同步注册,立刻生效;SubscribeAsync 是异步方法,实际仍走同步订阅流程,只是包装了 Task。官方文档明确说它“does not actually do anything asynchronously”,所以别被名字误导。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 不要在 ASP.NET Core 请求作用域里用
SubscribeAsync想“等一等再订”,它不会帮你延迟订阅时机 - 如果要动态增删频道,用
Unsubscribe/UnsubscribeAll,但注意:这些操作不是立即生效,Redis 协议本身不保证取消的实时性,尤其在网络波动时可能仍有残留消息到达 - 单个
ISubscriber实例可同时订阅多个频道,用Subscribe("chan1", "chan2", ...)或多次调用,但所有回调共享同一线程上下文(默认是 ThreadPool 线程),高并发时需自行加锁或使用ChannelReader转发
发布端用 IDatabase.Publish 还是 ISubscriber.Publish?
都可以,但语义和资源占用不同:IDatabase.Publish 复用普通命令连接池,轻量;ISubscriber.Publish 则强制走 PUBSUB 连接(即监听用的那个 socket),会竞争监听带宽,不推荐。
- 生产环境一律用
IDatabase.Publish发布,它是标准做法 -
IDatabase.Publish返回long表示收到该消息的订阅者数量,可用于简单监控,但注意:这个数只反映 Redis 服务端当时在线的订阅客户端数,不包括网络传输中丢失的情况 - 如果发布的是二进制数据(如 Protobuf),确保订阅端和发布端约定好
RedisValue的序列化方式,RedisValue本身不自动处理编码,byte[]和string混用会导致乱码
最易被忽略的一点:StackExchange.Redis 的 PUBSUB 不支持模式订阅(PSUBSCRIBE)的通配符匹配,比如 sub.Subscribe("log.*") 会当作字面量频道名处理。真要模式匹配,得换用其他客户端或自己在应用层做路由分发。










