Redis 消息发布订阅:轻量实时通知

FreeGuideOnline 最新 2026-06-16

Redis 发布订阅:轻量级实时通知系统

在现代应用架构中,实时消息推送几乎是标配需求。无论是聊天消息、系统广播,还是数据变更通知,你都需要一种高效、低延迟的通信机制。Redis 除了作为缓存和数据库,还内置了一套简单却强大的**发布/订阅(Pub/Sub)**功能,让你无需引入 RabbitMQ、Kafka 等重型中间件,就能快速构建轻量级的实时通知系统。

本教程从零开始,带你理解 Redis Pub/Sub 的核心概念、命令操作、适用场景以及实际使用中必须注意的陷阱。无论你是后端新手,还是想为系统添加实时功能的开发者,都能快速上手。

什么是发布订阅模式?

发布订阅是一种消息传递范式:消息的发送者(发布者)不直接把消息发给特定接收者(订阅者),而是将消息发送到某个频道(Channel)。对该频道感兴趣的所有订阅者都能收到消息,发布者和订阅者之间完全解耦,互不知晓对方的存在。

这种模式特别适合一对多的广播通知场景。Redis 将这一模式内置为服务器功能,你只需使用几条命令即可实现。

核心概念与命令

Redis Pub/Sub 涉及三个角色:

  • 发布者(Publisher):使用 PUBLISH 命令向频道发送消息。
  • 订阅者(Subscriber):使用 SUBSCRIBE 命令订阅一个或多个频道,之后会持续接收推送。
  • 频道(Channel):消息的通道,由字符串名称标识,无需预先创建,订阅或发布时自动创建。

基础命令详解

SUBSCRIBE channel [channel ...]

客户端进入订阅状态,监听指定频道。一旦订阅,客户端就无法再执行其他常规命令(除了 (UN)SUBSCRIBEPINGQUIT 等),直到主动退订。

示例(在 redis-cli 中操作):

# 客户端1:订阅 news 和 sports 频道
SUBSCRIBE news sports

执行后,客户端会收到类似这样的反馈:

1) "subscribe"
2) "news"
3) (integer) 1

表示成功订阅 news,当前订阅频道总数为 1。

PUBLISH channel message

向频道发送消息。返回值为接收到消息的订阅者数量(注意:没有订阅者时返回 0,消息直接丢弃)。

# 客户端2:发布消息
PUBLISH news "Breaking: Redis 7.4 released!"

此时客户端1会实时输出:

1) "message"
2) "news"
3) "Breaking: Redis 7.4 released!"
UNSUBSCRIBE [channel ...]

退订指定频道,若不提供频道名则退订所有频道。退订后客户端退出订阅状态,可重新执行常规命令。

# 退订 news 频道
UNSUBSCRIBE news

模式匹配订阅 PSUBSCRIBE

除了精确订阅频道名,Redis 还支持基于 glob 风格的模式匹配,订阅符合模式的一批频道。

  • PSUBSCRIBE pattern [pattern ...]:订阅匹配模式的频道。
  • PUNSUBSCRIBE [pattern ...]:退订模式。

模式示例:

  • news.* 匹配 news.sportsnews.finance
  • chat:* 匹配所有以 chat: 开头的频道
  • * 匹配所有频道
# 订阅所有 news 开头的频道
PSUBSCRIBE news.*

当有消息发送到 news.sports 时,模式订阅者会收到:

1) "pmessage"          # 表示模式消息
2) "news.*"            # 匹配的模式
3) "news.sports"       # 实际频道
4) "Match result"      # 消息内容

注意:若客户端同时使用了普通订阅和模式订阅,消息不会重复投递。但对于同一个消息,每个普通订阅客户端和模式订阅客户端都会各自收到一份。

消息流与机制特性

即发即弃(Fire-and-Forget)

Redis Pub/Sub 是一种“尽力交付”的消息系统,没有任何持久化或确认机制。消息一旦被 PUBLISH 发出,就会被推送给当前在线的订阅者,然后立即丢弃。如果某个订阅者在消息发布时处于离线、断连,或者网络抖动导致未收到,消息将永久丢失。

无消息回溯

与 Kafka 的消费者偏移量不同,Redis 不追踪订阅者读取到了哪里。新加入的订阅者只能收到订阅之后发布的消息,无法获取历史消息。

订阅连接独占

一旦客户端进入订阅状态(通过 SUBSCRIBEPSUBSCRIBE),连接就被转换为Pub/Sub专用,只能使用订阅相关命令。大部分客户端库会单独使用一条连接来处理订阅,以免阻塞常规命令操作。

实战演示:实时弹幕通知系统

假设我们正在构建一个直播平台的实时弹幕功能。每个直播间对应一个频道,客户端进入直播间时订阅该频道,退出时退订;服务器端负责发布弹幕。

1. Redis-cli 模拟

打开三个终端窗口:

  • 终端1(订阅者1):进入直播间 room:1001

    SUBSCRIBE room:1001
    
  • 终端2(订阅者2):同样订阅

    SUBSCRIBE room:1001
    
  • 终端3(发布者):发布弹幕

    PUBLISH room:1001 "用户A: 太帅了!"
    PUBLISH room:1001 "用户B: 哈哈哈哈"
    

终端1和终端2都会实时收到这两条消息。

2. Node.js 示例

使用 ioredis 库(或其他支持Pub/Sub的客户端):

const Redis = require('ioredis');

// 订阅者连接
const sub = new Redis();
// 发布者连接(也可以复用同一个实例,但推荐分开以保持订阅状态隔离)
const pub = new Redis();

// 订阅直播间频道
sub.subscribe('room:1001', (err, count) => {
  if (err) console.error('订阅失败', err);
  else console.log(`已订阅 ${count} 个频道`);
});

// 监听消息
sub.on('message', (channel, message) => {
  console.log(`收到[${channel}]消息: ${message}`);
  // 这里可将消息推送到前端 WebSocket
});

// 模拟发布
setInterval(() => {
  pub.publish('room:1001', `系统消息: 时间戳 ${Date.now()}`);
}, 3000);

适用场景与局限性

适合使用

  • 实时通知:站内信、系统警告、配置热更新广播。
  • 聊天与协作:小型聊天室、多人在线白板事件同步。
  • 解耦触发:数据变更通知缓存失效(“写时失效”模式),但需注意消息丢失风险。
  • 分布式服务间简单通信:微服务中的轻量事件广播(如清除本地缓存)。

不适合使用

  • 消息可靠性要求高的场景:订单支付状态流转、重要任务分发。应改用 Redis Streams 或专业消息队列。
  • 大量历史消息回溯:新节点上线需要重放数据的场景。
  • 大消息/高吞吐持久化:Pub/Sub 内存压力大,无确认机制容易堵塞。

进阶话题:Redis Sharded Pub/Sub

Redis 7.0 引入了分片发布/订阅(Sharded Pub/Sub),旨在解决集群模式下普通 Pub/Sub 广播消息的性能瓶颈。在集群中,普通 PUBLISH 会向集群中所有节点广播消息,即使订阅者只连接一个节点,也会导致跨节点流量爆炸。而 SPUBLISH 命令将消息限制在特定槽(slot)对应的节点上,大幅提升吞吐。使用时频道会像键一样被哈希到槽,仅在同一槽的节点间传递消息,适用于 Redis 集群环境下的高并发消息推送。

相关命令:SSUBSCRIBESPUBLISHSUNSUBSCRIBE。此部分属于高级特性,入门阶段暂不详细展开,只需知道当你将 Pub/Sub 用于大规模集群时,可以考虑这一优化。

常见问题排查

  • 订阅者收不到消息:确认订阅是否在 PUBLISH 之前执行;检查 PUBLISH 返回值,若为 0 说明当时无订阅者;检查频道名是否拼写一致。
  • 订阅后连接无法执行 GET 等命令:这是正常现象,需使用独立连接处理订阅。
  • 高并发下消息延迟:检查 Redis 服务器 CPU 及网络,Pub/Sub 推送是同步复制给每个订阅者客户端,慢速客户端可能拖慢整个发布过程(可通过客户端输出缓冲区限制处理)。
  • 消息丢失:这就不是 Bug,而是特性。如需可靠消息,改用 Redis Streams。

总结

Redis 的发布订阅以其极简的设计,为开发者提供了开箱即用的实时消息广播能力。它没有复杂的配置,三条命令即可打通消息通道,非常适合构建轻量级、低延迟、一对多的通知系统。但请始终记住它的“即发即弃”和“无持久化”特性,切勿在要求消息可靠传递的场景中过度依赖。当你需要消息确认、回溯和消费者组时,邻居“Redis Streams”会是更好的进阶选择。

现在,打开你的 redis-cli,试着发布第一条“Hello, Pub/Sub”吧!