redis如何实现发布/订阅功能?

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!


Redis 的发布/订阅(Pub/Sub)功能是一种基于消息的通信模式,允许客户端通过频道(Channel)或模式(Pattern)实时传递消息。以下是其实现原理和核心机制的深度分析:

一、Pub/Sub 的核心概念

  1. 角色

    • 发布者(Publisher):向频道发送消息的客户端。
    • 订阅者(Subscriber):订阅频道并接收消息的客户端。
    • 频道(Channel):消息传递的媒介,订阅者需明确指定订阅的频道。
    • 模式(Pattern) :支持通配符(如 news.*)的批量订阅方式。
  2. 特点

    • 实时性:消息即发即收,无持久化(消息不存储,离线订阅者无法获取历史消息)。
    • 无状态:Redis 不跟踪订阅者状态,仅负责消息路由。

二、底层实现机制

1. 数据结构

Redis 使用 字典(Hash Table)链表 管理 Pub/Sub 的订阅关系:

  • 频道订阅字典pubsub_channels

    • Key:频道名称(如 news.sports)。
    • Value:订阅该频道的客户端链表(保存客户端指针)。
    c 复制代码
    struct redisServer {
        dict *pubsub_channels;  // 频道订阅字典
        list *pubsub_patterns;  // 模式订阅链表
    };
  • 模式订阅链表pubsub_patterns
    存储包含通配符的订阅关系,每个节点保存:

    • 客户端指针。
    • 订阅的模式(如 news.*)。

2. 订阅流程

  • 订阅频道(SUBSCRIBE)

    1. 检查 pubsub_channels 字典中是否存在该频道键。
    2. 若不存在,创建新键并将客户端添加到链表;若存在,直接追加到链表末尾。
    bash 复制代码
    SUBSCRIBE news.sports  # 客户端订阅频道
  • 订阅模式(PSUBSCRIBE)

    1. 将客户端和模式添加到 pubsub_patterns 链表。
    bash 复制代码
    PSUBSCRIBE news.*      # 订阅所有以 `news.` 开头的频道

3. 发布流程(PUBLISH)

  1. 发送到频道订阅者

    • pubsub_channels 字典中查找频道对应的客户端链表。
    • 遍历链表,向每个客户端发送消息。
  2. 发送到模式匹配的订阅者

    • 遍历 pubsub_patterns 链表,检查频道名称是否匹配模式(如 news.sports 匹配 news.*)。
    • 向匹配的客户端发送消息。
    bash 复制代码
    PUBLISH news.sports "Match started!"  # 发布消息到频道

4. 取消订阅

  • 取消频道(UNSUBSCRIBE):从链表中移除客户端,若链表为空则删除频道键。
  • 取消模式(PUNSUBSCRIBE) :从 pubsub_patterns 链表中移除对应节点。

三、关键特性与限制

1. 实时性与无持久化

  • 优点:消息延迟极低(通常 < 1ms)。
  • 缺点
    • 订阅者断开连接后重新上线无法获取错过的消息。
    • 消息无备份,Redis 崩溃后消息丢失。

2. 性能表现

  • 时间复杂度
    • 发布:O(N+M),其中 N 是频道订阅者数量,M 是匹配的模式订阅者数量。
    • 订阅/取消订阅:O(1)。
  • 吞吐量:单节点可支持约 10万/秒的 Pub/Sub 操作(依赖消息大小和客户端数量)。

3. 与 Stream 的对比

特性 Pub/Sub Stream
消息持久化 不支持 支持(消息存储在内存中)
消费者状态 无状态 支持消费者组和消息确认(ACK)
回溯消费 不可行 支持读取历史消息
适用场景 实时通知、事件广播 消息队列、日志收集

四、应用场景与示例

1. 实时通知系统

bash 复制代码
# 订阅者A
SUBSCRIBE notifications

# 订阅者B(通配符订阅)
PSUBSCRIBE notifications.*

# 发布者
PUBLISH notifications "New message!"  # 所有订阅者收到消息

2. 聊天室

python 复制代码
# Python 发布示例
import redis
r = redis.Redis()
r.publish('chat:room1', 'Hello, everyone!')

# 订阅者(需另启线程监听)
pubsub = r.pubsub()
pubsub.subscribe('chat:room1')
for message in pubsub.listen():
    print(message['data'])

3. 微服务间事件驱动

  • 服务A 发布订单创建事件:

    bash 复制代码
    PUBLISH order:created "{id: 123, user: 'Alice'}"
  • 服务B 订阅事件并处理:

    bash 复制代码
    SUBSCRIBE order:created

五、生产环境注意事项

  1. 避免频道过载
    • 单个频道的订阅者不宜过多(建议 < 1万),否则发布时遍历链表开销大。
  2. 网络带宽控制
    • 大消息(如 > 1KB)会阻塞网络,建议拆分或改用 Stream。
  3. 客户端重连机制
    • 订阅者需实现断线自动重订阅逻辑(如监听 connect 事件)。
  4. 监控
    • 使用 PUBSUB CHANNELS 查看活跃频道:

      bash 复制代码
      PUBSUB CHANNELS "news.*"  # 统计匹配模式的频道
      PUBSUB NUMSUB news.sports # 查看频道订阅数

六、高级用法

1. 集群模式下的 Pub/Sub

  • Redis 集群中,Pub/Sub 消息仅在当前节点广播,无法跨节点。
  • 解决方案:
    • 所有订阅者连接到同一节点。
    • 使用全局消息代理(如 Redis 的 Sharded Pub/Sub 或第三方工具)。

2. 结合 Lua 脚本

通过脚本实现条件发布:

lua 复制代码
-- 仅当条件满足时发布消息
if redis.call("GET", "flag") == "1" then
    redis.call("PUBLISH", "alerts", "Triggered!")
end

总结

Redis 的 Pub/Sub 通过 字典 + 链表 的轻量级设计,实现了高效的消息广播,适合实时性要求高但容忍消息丢失的场景。对于需要持久化或可靠消费的场景,应选择 Stream 或专业消息队列(如 Kafka)。合理使用 Pub/Sub 可以构建灵活的实时通信系统,但需注意其无状态特性带来的局限性。

相关推荐
一只小松许️13 分钟前
Rust泛型与特性
java·开发语言·rust
_一条咸鱼_5 小时前
AI 大模型的 MCP 原理
人工智能·深度学习·面试
_一条咸鱼_5 小时前
AI 大模型 Function Calling 原理
人工智能·深度学习·面试
angushine5 小时前
Gateway获取下游最终响应码
java·开发语言·gateway
爱的叹息5 小时前
关于 JDK 中的 jce.jar 的详解,以及与之功能类似的主流加解密工具的详细对比分析
java·python·jar
小陈同学呦5 小时前
聊聊双列瀑布流
前端·javascript·面试
一一Null5 小时前
Token安全存储的几种方式
android·java·安全·android studio
来自星星的坤5 小时前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc6 小时前
RaabitMQ 快速入门
java·后端·rabbitmq