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 可以构建灵活的实时通信系统,但需注意其无状态特性带来的局限性。

相关推荐
vx1_Biye_Design2 分钟前
基于Spring Boot+Vue的学生管理系统设计与实现-计算机毕业设计源码46223
java·vue.js·spring boot·spring·eclipse·tomcat·maven
vx_Biye_Design3 分钟前
基于Spring Boot+vue的湖北旅游景点门票预约平台的设计--毕设附源码29593
java·vue.js·spring boot·spring cloud·servlet·eclipse·课程设计
hdsoft_huge8 分钟前
1panel面板中部署SpringBoot和Vue前后端分离系统 【图文教程】
vue.js·spring boot·后端
hay_lee21 分钟前
Spring AI实现对话聊天-流式输出
java·人工智能·ollama·spring ai
Hx_Ma1627 分钟前
SpringBoot数据源自动管理
java·spring boot·spring
SunnyDays101128 分钟前
Java 高效实现 CSV 转 Excel
java·csv转excel
starfire_hit29 分钟前
JAVAWEB根据前台请求获取用户IP
java·服务器·网络
fengxin_rou31 分钟前
[Redis从零到精通|第四篇]:缓存穿透、雪崩、击穿
java·redis·缓存·mybatis·idea·多线程
像少年啦飞驰点、34 分钟前
从零开始学 RabbitMQ:小白也能懂的消息队列实战指南
java·spring boot·微服务·消息队列·rabbitmq·异步编程
lekami_兰36 分钟前
RabbitMQ 延迟队列实现指南:两种方案手把手教你搞定
后端·rabbitmq·延迟队列