深入理解 Redis Pub/Sub:一篇完整的介绍与使用指南
Redis 是一个广受欢迎的内存数据结构存储系统,它不仅仅是一个简单的键值对数据库。除了高性能的缓存、队列、集合等功能外,Redis 还提供了一个强大但经常被误解的特性:发布/订阅(Pub/Sub)。
本文将带你深入了解 Redis Pub/Sub 的工作原理、核心概念、使用方法,并探讨其优缺点和适用场景。
什么是发布/订阅(Pub/Sub)?
发布/订阅是一种消息通信模式,它包含两个核心角色:
- 发布者(Publisher): 消息的发送方。发布者将消息发送到特定的“频道”(Channel),但它不关心谁会接收这些消息。
- 订阅者(Subscriber): 消息的接收方。订阅者可以对一个或多个频道表现出兴趣,并“订阅”它们。当有消息被发布到这些频道时,所有订阅了该频道的订阅者都会收到这个消息的副本。
这种模式最大的特点是解耦(Decoupling)。发布者和订阅者之间没有直接的联系,它们通过一个中介——频道——进行通信。发布者不需要知道订阅者的存在,订阅者也不需要知道发布者的信息。

(图片来源: redis.io)
Redis Pub/Sub 的核心机制
在 Redis 中,Pub/Sub 机制允许客户端订阅任意数量的频道,从而实现实时的消息传递。
关键命令
Redis Pub/Sub 的实现主要依赖以下几个命令:
PUBLISH channel message: 将message发布到指定的channel。任何订阅了该频道的客户端都会收到此消息。返回值为接收到此消息的订阅者数量。SUBSCRIBE channel [channel ...]: 订阅一个或多个指定的频道。一旦客户端进入订阅状态,它就只能接收与 Pub/Sub 相关的消息,不能执行其他命令(除了UNSUBSCRIBE和PSUBSCRIBE等)。UNSUBSCRIBE [channel [channel ...]]: 退订一个或多个频道。如果没有指定频道,则退订所有已订阅的频道。PSUBSCRIBE pattern [pattern ...]: 订阅一个或多个与pattern匹配的频道。这里的模式支持*通配符,例如news.*可以匹配news.tech、news.sports等所有以news.开头的频道。PUNSUBSCRIBE [pattern [pattern ...]]: 退订指定的模式。
工作流程
- 一个或多个 Redis 客户端(订阅者)执行
SUBSCRIBE或PSUBSCRIBE命令,开始监听特定的频道或模式。 - 另一个 Redis 客户端(发布者)执行
PUBLISH命令,向某个频道发送消息。 - Redis 服务器接收到消息后,会立即将其转发给所有订阅了该频道的客户端。
- 这个过程是“即发即弃”(Fire and Forget)的。如果消息发布时没有任何订阅者,那么这条消息就会丢失,不会被存储或保留。
使用指南:Python 实战示例
下面我们通过 Python 的 redis-py 库来演示如何使用 Redis Pub/Sub。首先,请确保你已经安装了该库:
bash
pip install redis
1. 发布者 (publisher.py)
发布者负责向频道发送消息。代码非常简单:
“`python
import redis
import time
连接到 Redis
decode_responses=True 会将从 Redis 接收的二进制数据自动解码为 UTF-8 字符串
r = redis.Redis(host=’localhost’, port=6379, decode_responses=True)
channel_name = ‘news_channel’
print(f”开始向频道 ‘{channel_name}’ 发布消息…”)
for i in range(5):
message = f”这是第 {i+1} 条新闻!”
print(f”发布: ‘{message}'”)
# PUBLISH 命令返回接收到消息的订阅者数量
subscriber_count = r.publish(channel_name, message)
print(f” (有 {subscriber_count} 个订阅者接收到了此消息)”)
time.sleep(2)
发布一条结束消息
r.publish(channel_name, ‘STOP’)
print(“发布结束。”)
“`
2. 订阅者 (subscriber.py)
订阅者会持续监听频道,直到收到特定的停止信号。
“`python
import redis
连接到 Redis
r = redis.Redis(host=’localhost’, port=6379, decode_responses=True)
channel_name = ‘news_channel’
创建一个 PubSub 对象
p = r.pubsub()
订阅频道
p.subscribe(channel_name)
print(f”已订阅频道 ‘{channel_name}’,等待消息…”)
持续监听消息
for message in p.listen():
# message 是一个字典,包含 type, channel, data 等键
# 刚订阅时会收到一个 ‘subscribe’ 类型的消息
if message[‘type’] == ‘message’:
print(f”收到消息: ‘{message[‘data’]}'”)
# 如果收到 'STOP' 消息,则退出循环
if message['data'] == 'STOP':
print("收到停止信号,取消订阅。")
break
取消订阅并关闭连接
p.unsubscribe()
p.close()
print(“订阅已结束。”)
“`
如何运行
- 打开一个终端,运行订阅者脚本:
python subscriber.py - 打开另一个终端,运行发布者脚本:
python publisher.py
你将看到,当发布者发送消息时,订阅者的终端会几乎同时打印出接收到的消息。
优点与局限性
优点
- 实时性: 消息的发布和接收几乎没有延迟,非常适合需要实时通信的场景。
- 简单易用: API 非常简单,只有几个核心命令,上手快。
- 完全解耦: 发布者和订阅者相互独立,系统灵活性和可扩展性高。你可以随时增加或减少发布者和订阅者,而无需修改其他部分的代码。
局限性
- 消息不保证送达(At-Most-Once Delivery): 这是 Redis Pub/Sub 最重要的一个特性。如果订阅者在消息发布时掉线或尚未连接,它将永远不会收到这条消息。Redis 不会为离线的订阅者保留消息。
- 无消息持久化: 消息是即时发送的,不会在 Redis 中存储。如果需要持久化、有历史消息追溯能力,Pub/Sub 并不适合。
- 订阅者状态:
SUBSCRIBE是一个阻塞命令。一旦客户端进入订阅状态,它就不能执行其他非 Pub/Sub 命令,通常需要为订阅者创建独立的连接。 - 无消息确认机制: 发布者不知道消息是否被订阅者成功处理。
何时使用 Redis Pub/Sub?
基于其优缺点,Redis Pub/Sub 特别适用于以下场景:
- 实时通知系统: 例如,当一个新用户注册时,向所有管理员客户端发送通知。
- 实时聊天室/弹幕: 多个用户订阅同一个聊天室频道,用户发送的消息被发布到该频道,所有订阅者都能收到。
- 数据分发: 将数据(如股价、系统指标)实时分发给多个客户端或服务进行处理。
- 触发器: 当某个事件发生时(如缓存失效),通过 Pub/Sub 通知其他进程进行相应操作(如更新缓存)。
替代方案:Redis Streams
如果你发现 Redis Pub/Sub 的“消息不持久化”和“不保证送达”的特性无法满足你的需求,那么你应该考虑使用 Redis 5.0 之后引入的 Redis Streams。
Redis Streams 是一个功能更强大的消息队列系统,它提供了:
- 消息持久化: 消息会被保存在内存中。
- 消费组(Consumer Groups): 允许多个消费者协作处理同一个流中的消息,实现负载均衡和故障转移。
- 消息确认(ACK): 确保消息被成功处理。
- 历史消息查询: 可以从任意位置开始读取消息。
简而言之,当可靠性比实时性更重要时,Redis Streams 是更好的选择。
总结
Redis Pub/Sub 是一个轻量级、高性能的实时消息传递系统。它的核心优势在于简单和快速,通过解耦发布者和订阅者,极大地提高了系统的灵活性。然而,它“即发即弃”的特性也决定了它不适用于需要高可靠性、消息持久化和复杂消息处理的场景。
在选择是否使用 Redis Pub/Sub 时,请务必仔细评估你的业务需求:如果你需要一个简单的实时通知机制,并且可以容忍偶尔的消息丢失,那么它是一个绝佳的选择。否则,功能更完备的 Redis Streams 或其他专业的消息队列(如 RabbitMQ, Kafka)可能更适合你。