AIGC标识 Redis 发布订阅(Pub/Sub)

Redis 发布订阅(Pub/Sub)


一、Pub/Sub 是什么

想象一下:订单服务刚创建了一笔订单,库存系统要减库存、通知服务要发短信、分析平台要记日志——如果每个服务之间都直接调 HTTP,当第四个消费者加入时所有上游都得改代码。这种网状调用会随着系统变大而崩溃。

发布订阅(Pub/Sub)翻转了这种模型:

Publisher ──(msg)──> Redis ──(fan-out)──> Subscriber A
                                      ──> Subscriber B
                                      ──> Subscriber C

核心思想:发布者只管往"频道"扔消息,不关心谁在听。订阅者只管从频道收消息,不关心谁发的。Redis 作为中间人负责路由。


二、Redis Pub/Sub 的三个核心命令

命令 作用
PUBLISH channel message 向频道发送消息,返回收到消息的订阅者数量
SUBSCRIBE channel [channel ...] 订阅一个或多个频道(精确匹配)
PSUBSCRIBE pattern 订阅匹配模式的所有频道(如 order:*

一个关键性质:消息不落地。Redis 不存储已发送的消息——如果订阅者掉线了,错过就是错过了。这是 Pub/Sub 和消息队列(RabbitMQ/Kafka)的根本区别。


三、Go 中的订阅与发布

订阅者

// 订阅者开启一个专用长连接,持续接收消息
pubsub := rdb.Subscribe(ctx, "orders:new", "payments:done")
defer pubsub.Close()

// Channel() 返回一个 Go channel,把 Redis 的网络流转换为本地 channel
ch := pubsub.Channel()

// 循环读消息(阻塞直到来消息或 ctx 取消)
for msg := range ch {
    fmt.Printf("[%s] %s\n", msg.Channel, msg.Payload)
}

关键细节:Subscribe() 后 Redis 客户端会分配一条专用 TCP 连接,这条连接被锁定在 Pub/Sub 模式,不能再执行普通命令(GET/SET 之类的)。这也是为什么生产环境通常给 Pub/Sub 单独配一个连接池。

发布者

// 发布一个 JSON 消息
type OrderEvent struct {
    OrderID string  `json:"order_id"`
    Amount  float64 `json:"amount"`
    Status  string  `json:"status"`
}

event, _ := json.Marshal(OrderEvent{OrderID: "O-1024", Amount: 99.9, Status: "paid"})
n, _ := rdb.Publish(ctx, "orders:new", event).Result()
fmt.Printf("消息触达 %d 个订阅者\n", n)

PUBLISH 的返回值是收到消息的订阅者数量——如果返回 0,说明没人监听,消息丢了。这是 Pub/Sub 的"fire-and-forget"天性。

模式订阅:用通配符监听一批频道

// 订阅所有 order 相关事件:orders:created, orders:cancelled, orders:paid...
pubsub := rdb.PSubscribe(ctx, "orders:*")
ch := pubsub.Channel()

for msg := range ch {
    fmt.Printf("[%s → %s] %s\n", msg.Pattern, msg.Channel, msg.Payload)
}

* 匹配一个层级,如 order:* 匹配 order:new 但不匹配 order:item:stock。如果需要多级匹配可以用 order:**(取决于 Redis 版本)。


四、Pub/Sub 的消息可靠性问题

Pub/Sub 设计上是一条"广播水管"——水流过就没了。什么时候会丢消息?

场景 后果
订阅者未上线时发布消息 消息丢失
订阅者的 Channel 缓冲区满(处理慢) 消息积压,Redis 客户端可能主动断连
网络闪断期间的消息 丢失
Redis 宕机重启 所有订阅关系 + 未发消息全部消失

解决方案:Pub/Sub + Stream 双通道

Pub/Sub 负责实时推送,Stream 负责消息持久化

Publisher ──PUBLISH──> Redis Pub/Sub ──实时推送──> Subscriber
         ──XADD────> Redis Stream ──补偿读取──> Subscriber(掉线重连后)
  • 在线时:吃 Pub/Sub 的实时推送(低延迟)
  • 掉线后:从 Stream 里按消费者组的 LastDeliveredID 往回拉取遗漏的消息

这不是 Redis 内建功能,而是应用层组合拳。


五、Pub/Sub vs 其他消息模式

维度 Pub/Sub Redis Streams RabbitMQ Kafka
消息持久化 ❌ 不持久化
消费确认 ❌ 无 ✅ XACK ✅ ACK ✅ offset commit
回溯重复消费
延迟 < 1ms < 1ms ~ ms 级 ~ ms 级
运维复杂度 极低
适用场景 实时广播、缓存失效通知、WebSocket 跨节点同步 事件日志、消息队列、消费者组 企业消息中间件 大数据流处理

一句话选型:要实时 + 丢几条没事 → Pub/Sub;要可靠 + 不能丢 → Streams。


六、常见应用场景

1. 跨节点缓存失效

多个 Web 节点各自有本地缓存,数据更新后通过 Pub/Sub 广播一个 cache:invalidate:user:42,所有节点同时清掉本地缓存。这是 Redis Pub/Sub 最经典的生产场景。

2. WebSocket 跨节点消息推送

用户连在节点 A 的 WebSocket 上,但触发消息的服务跑在节点 B 上。B 发 Pub/Sub 到 Redis,A 订阅并推给 WebSocket 客户端。Socket.IO 的 Redis Adapter 就是这个原理。

3. 配置热更新

配置中心更新配置后,PUBLISH 一条通知。所有服务订阅了 config:reload,收到后立刻拉取最新配置。


七、本章要点

要点 一句话
Pub/Sub 是广播,不是队列 消息发完就消失,没有重放、没有回执
一条长连接一个 PubSub Subscribe 后连接被专用,不要混用
PSubscribe 用 * 通配 可以监听整批频道,省去逐个 SUBSCRIBE
可靠性靠组合拳 Pub/Sub 做实时通道,Stream 做补偿兜底
延迟极低 亚毫秒级,适合实时场景

核心认知:把 Pub/Sub 当成一个"广播喇叭"而非"消息仓库"来用,就对了。

posted @ 2026-08-25 09:47  FfHUCisI  阅读(7)  评论(0)    收藏  举报