Redis 消息发布订阅:轻量实时通知
Redis 发布订阅:轻量级实时通知系统
在现代应用架构中,实时消息推送几乎是标配需求。无论是聊天消息、系统广播,还是数据变更通知,你都需要一种高效、低延迟的通信机制。Redis 除了作为缓存和数据库,还内置了一套简单却强大的**发布/订阅(Pub/Sub)**功能,让你无需引入 RabbitMQ、Kafka 等重型中间件,就能快速构建轻量级的实时通知系统。
本教程从零开始,带你理解 Redis Pub/Sub 的核心概念、命令操作、适用场景以及实际使用中必须注意的陷阱。无论你是后端新手,还是想为系统添加实时功能的开发者,都能快速上手。
什么是发布订阅模式?
发布订阅是一种消息传递范式:消息的发送者(发布者)不直接把消息发给特定接收者(订阅者),而是将消息发送到某个频道(Channel)。对该频道感兴趣的所有订阅者都能收到消息,发布者和订阅者之间完全解耦,互不知晓对方的存在。
这种模式特别适合一对多的广播通知场景。Redis 将这一模式内置为服务器功能,你只需使用几条命令即可实现。
核心概念与命令
Redis Pub/Sub 涉及三个角色:
- 发布者(Publisher):使用
PUBLISH命令向频道发送消息。 - 订阅者(Subscriber):使用
SUBSCRIBE命令订阅一个或多个频道,之后会持续接收推送。 - 频道(Channel):消息的通道,由字符串名称标识,无需预先创建,订阅或发布时自动创建。
基础命令详解
SUBSCRIBE channel [channel ...]
客户端进入订阅状态,监听指定频道。一旦订阅,客户端就无法再执行其他常规命令(除了 (UN)SUBSCRIBE、PING、QUIT 等),直到主动退订。
示例(在 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.sports、news.financechat:*匹配所有以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 不追踪订阅者读取到了哪里。新加入的订阅者只能收到订阅之后发布的消息,无法获取历史消息。
订阅连接独占
一旦客户端进入订阅状态(通过 SUBSCRIBE 或 PSUBSCRIBE),连接就被转换为Pub/Sub专用,只能使用订阅相关命令。大部分客户端库会单独使用一条连接来处理订阅,以免阻塞常规命令操作。
实战演示:实时弹幕通知系统
假设我们正在构建一个直播平台的实时弹幕功能。每个直播间对应一个频道,客户端进入直播间时订阅该频道,退出时退订;服务器端负责发布弹幕。
1. Redis-cli 模拟
打开三个终端窗口:
-
终端1(订阅者1):进入直播间
room:1001SUBSCRIBE 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 集群环境下的高并发消息推送。
相关命令:SSUBSCRIBE、SPUBLISH、SUNSUBSCRIBE。此部分属于高级特性,入门阶段暂不详细展开,只需知道当你将 Pub/Sub 用于大规模集群时,可以考虑这一优化。
常见问题排查
- 订阅者收不到消息:确认订阅是否在
PUBLISH之前执行;检查PUBLISH返回值,若为 0 说明当时无订阅者;检查频道名是否拼写一致。 - 订阅后连接无法执行
GET等命令:这是正常现象,需使用独立连接处理订阅。 - 高并发下消息延迟:检查 Redis 服务器 CPU 及网络,Pub/Sub 推送是同步复制给每个订阅者客户端,慢速客户端可能拖慢整个发布过程(可通过客户端输出缓冲区限制处理)。
- 消息丢失:这就不是 Bug,而是特性。如需可靠消息,改用 Redis Streams。
总结
Redis 的发布订阅以其极简的设计,为开发者提供了开箱即用的实时消息广播能力。它没有复杂的配置,三条命令即可打通消息通道,非常适合构建轻量级、低延迟、一对多的通知系统。但请始终记住它的“即发即弃”和“无持久化”特性,切勿在要求消息可靠传递的场景中过度依赖。当你需要消息确认、回溯和消费者组时,邻居“Redis Streams”会是更好的进阶选择。
现在,打开你的 redis-cli,试着发布第一条“Hello, Pub/Sub”吧!