Appearance
第12章:Redis 发布与订阅(消息通知基础)
12.1 发布与订阅的意义
Redis发布与订阅(Pub/Sub)是一种消息通信模式,允许发送者(发布者)向特定频道发送消息,而接收者(订阅者)可以订阅一个或多个频道来接收消息。
核心意义:
- 实现系统间的解耦通信
- 支持实时消息通知
- 简化系统架构,减少直接依赖
典型应用场景:
- 系统通知:如登录提醒、操作通知
- 实时更新:如商品价格变动、库存变化
- 消息广播:如系统维护公告、活动通知
- 事件驱动:如用户注册成功后触发后续流程
12.2 核心命令
1. 频道订阅与发布
| 命令 | 功能 | 示例 |
|---|---|---|
| SUBSCRIBE | 订阅一个或多个频道 | SUBSCRIBE channel1 channel2 |
| PUBLISH | 向频道发布消息 | PUBLISH channel1 "Hello Redis" |
| UNSUBSCRIBE | 取消订阅一个或多个频道 | UNSUBSCRIBE channel1 |
| PSUBSCRIBE | 订阅匹配模式的频道 | PSUBSCRIBE news:* |
| PUNSUBSCRIBE | 取消订阅匹配模式的频道 | PUNSUBSCRIBE news:* |
2. 命令使用示例
示例1:基本的发布与订阅
订阅者端:
bash
# 连接Redis并订阅频道
redis-cli
# 订阅名为 "notice" 的频道
SUBSCRIBE notice
# 等待接收消息...
# 当有消息发布时,会显示如下格式:
# 1) "message"
# 2) "notice"
# 3) "Hello from publisher!"发布者端:
bash
# 连接Redis并发布消息
redis-cli
# 向 "notice" 频道发布消息
PUBLISH notice "Hello from publisher!"
# 返回值表示有多少个订阅者接收到了消息
# (integer) 1示例2:使用模式匹配订阅多个频道
订阅者端:
bash
# 订阅所有以 "news:" 开头的频道
PSUBSCRIBE news:*发布者端:
bash
# 向不同的新闻频道发布消息
PUBLISH news:sports "Sports news update"
PUBLISH news:tech "Technology news update"
PUBLISH news:finance "Financial news update"12.3 发布与订阅的工作原理
订阅过程:
- 订阅者向Redis服务器发送SUBSCRIBE命令
- Redis服务器将订阅者与频道关联起来
- 订阅者进入阻塞状态,等待接收消息
发布过程:
- 发布者向Redis服务器发送PUBLISH命令
- Redis服务器查找订阅该频道的所有订阅者
- Redis服务器将消息发送给所有匹配的订阅者
消息传递:
- 消息是实时传递的,不存储
- 订阅者必须在线才能接收消息
- 消息一旦发送,无法回放或重发
12.4 应用场景实操
场景1:系统消息通知
需求:实现一个简单的系统消息通知功能,当管理员发布通知时,所有在线用户都能收到。
实现步骤:
用户端订阅通知频道:
javascript// 前端JavaScript代码 const redis = require('redis'); const subscriber = redis.createClient(); subscriber.subscribe('system:notice'); subscriber.on('message', (channel, message) => { console.log(`收到系统通知: ${message}`); // 显示通知到界面 });管理员发布通知:
bash# 管理员执行命令发布通知 redis-cli PUBLISH system:notice "系统将于今晚23:00进行维护,预计持续30分钟"
场景2:实时价格更新
需求:当商品价格变动时,实时通知前端更新显示。
实现步骤:
前端订阅价格频道:
javascript// 前端JavaScript代码 const subscriber = redis.createClient(); // 订阅特定商品的价格频道 subscriber.subscribe('price:product:1001'); subscriber.on('message', (channel, message) => { const priceData = JSON.parse(message); console.log(`商品${priceData.productId}价格更新为: ¥${priceData.price}`); // 更新界面价格显示 });后端发布价格更新:
javascript// 后端Node.js代码 const publisher = redis.createClient(); // 当价格变动时发布消息 function updatePrice(productId, newPrice) { const priceData = { productId: productId, price: newPrice, timestamp: new Date().toISOString() }; publisher.publish(`price:product:${productId}`, JSON.stringify(priceData)); } // 调用示例 updatePrice(1001, 99.99);
12.5 Redis Pub/Sub与其他消息队列的对比
| 特性 | Redis Pub/Sub | RabbitMQ | Kafka |
|---|---|---|---|
| 消息存储 | 不存储消息,实时传递 | 支持消息持久化 | 支持消息持久化 |
| 消息可靠性 | 低(订阅者离线则丢失) | 高(支持ACK机制) | 高(支持偏移量) |
| 消息顺序 | 保证频道内消息顺序 | 保证队列内消息顺序 | 保证分区内消息顺序 |
| 扩展性 | 有限 | 良好 | 优秀 |
| 复杂度 | 低(易于使用) | 中(配置复杂) | 高(学习曲线陡) |
| 适用场景 | 实时通知、简单消息 | 复杂业务场景 | 高吞吐量、大数据 |
12.6 注意事项与最佳实践
注意事项
- 消息不持久:Redis Pub/Sub不存储消息,订阅者离线期间的消息会丢失
- 无消息确认:发布者无法知道消息是否被接收
- 阻塞操作:订阅后客户端会进入阻塞状态,无法执行其他命令
- 性能限制:高并发场景下可能成为瓶颈
最佳实践
- 合理设计频道名称:使用命名空间和层次结构,如
system:notice、price:product:1001 - 消息格式标准化:使用JSON格式,包含必要的元数据
- 避免消息过大:消息大小应控制在合理范围内,避免影响性能
- 考虑使用专业消息队列:对于关键业务场景,考虑使用RabbitMQ或Kafka
新手易错点
- 订阅后无法执行其他命令:订阅操作会阻塞客户端,需要在单独的连接中进行
- 消息发布后未订阅无法接收:Pub/Sub是实时的,未订阅的客户端无法接收历史消息
- 频道名称拼写错误:发布和订阅的频道名称必须完全匹配
- 忘记取消订阅:长期运行的应用应在不需要时取消订阅,避免资源泄漏
- 过度依赖Pub/Sub:对于需要可靠传递的消息,应考虑使用专业的消息队列系统
