什么是 Redis Pub/Sub?一篇完整的介绍与使用指南 – wiki基地


深入理解 Redis Pub/Sub:一篇完整的介绍与使用指南

Redis 是一个广受欢迎的内存数据结构存储系统,它不仅仅是一个简单的键值对数据库。除了高性能的缓存、队列、集合等功能外,Redis 还提供了一个强大但经常被误解的特性:发布/订阅(Pub/Sub)

本文将带你深入了解 Redis Pub/Sub 的工作原理、核心概念、使用方法,并探讨其优缺点和适用场景。

什么是发布/订阅(Pub/Sub)?

发布/订阅是一种消息通信模式,它包含两个核心角色:

  1. 发布者(Publisher): 消息的发送方。发布者将消息发送到特定的“频道”(Channel),但它不关心谁会接收这些消息。
  2. 订阅者(Subscriber): 消息的接收方。订阅者可以对一个或多个频道表现出兴趣,并“订阅”它们。当有消息被发布到这些频道时,所有订阅了该频道的订阅者都会收到这个消息的副本。

这种模式最大的特点是解耦(Decoupling)。发布者和订阅者之间没有直接的联系,它们通过一个中介——频道——进行通信。发布者不需要知道订阅者的存在,订阅者也不需要知道发布者的信息。

Redis Pub/Sub
(图片来源: redis.io)

Redis Pub/Sub 的核心机制

在 Redis 中,Pub/Sub 机制允许客户端订阅任意数量的频道,从而实现实时的消息传递。

关键命令

Redis Pub/Sub 的实现主要依赖以下几个命令:

  • PUBLISH channel message: 将 message 发布到指定的 channel。任何订阅了该频道的客户端都会收到此消息。返回值为接收到此消息的订阅者数量。
  • SUBSCRIBE channel [channel ...]: 订阅一个或多个指定的频道。一旦客户端进入订阅状态,它就只能接收与 Pub/Sub 相关的消息,不能执行其他命令(除了 UNSUBSCRIBEPSUBSCRIBE 等)。
  • UNSUBSCRIBE [channel [channel ...]]: 退订一个或多个频道。如果没有指定频道,则退订所有已订阅的频道。
  • PSUBSCRIBE pattern [pattern ...]: 订阅一个或多个与 pattern 匹配的频道。这里的模式支持 * 通配符,例如 news.* 可以匹配 news.technews.sports 等所有以 news. 开头的频道。
  • PUNSUBSCRIBE [pattern [pattern ...]]: 退订指定的模式。

工作流程

  1. 一个或多个 Redis 客户端(订阅者)执行 SUBSCRIBEPSUBSCRIBE 命令,开始监听特定的频道或模式。
  2. 另一个 Redis 客户端(发布者)执行 PUBLISH 命令,向某个频道发送消息。
  3. Redis 服务器接收到消息后,会立即将其转发给所有订阅了该频道的客户端。
  4. 这个过程是“即发即弃”(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(“订阅已结束。”)
“`

如何运行

  1. 打开一个终端,运行订阅者脚本:python subscriber.py
  2. 打开另一个终端,运行发布者脚本:python publisher.py

你将看到,当发布者发送消息时,订阅者的终端会几乎同时打印出接收到的消息。

优点与局限性

优点

  1. 实时性: 消息的发布和接收几乎没有延迟,非常适合需要实时通信的场景。
  2. 简单易用: API 非常简单,只有几个核心命令,上手快。
  3. 完全解耦: 发布者和订阅者相互独立,系统灵活性和可扩展性高。你可以随时增加或减少发布者和订阅者,而无需修改其他部分的代码。

局限性

  1. 消息不保证送达(At-Most-Once Delivery): 这是 Redis Pub/Sub 最重要的一个特性。如果订阅者在消息发布时掉线或尚未连接,它将永远不会收到这条消息。Redis 不会为离线的订阅者保留消息。
  2. 无消息持久化: 消息是即时发送的,不会在 Redis 中存储。如果需要持久化、有历史消息追溯能力,Pub/Sub 并不适合。
  3. 订阅者状态: SUBSCRIBE 是一个阻塞命令。一旦客户端进入订阅状态,它就不能执行其他非 Pub/Sub 命令,通常需要为订阅者创建独立的连接。
  4. 无消息确认机制: 发布者不知道消息是否被订阅者成功处理。

何时使用 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)可能更适合你。

滚动至顶部