当前位置: 首页 > news >正文

Redis 发布订阅:社区的 “通知栏与分类订阅” 系统

目录

一、核心概念:社区的 “通知栏、订阅者与分类”

二、频道订阅:居民 “登记关注通知栏”(SUBSCRIBE/UNSUBSCRIBE)

1. 数据结构对应:社区的 “通知栏 - 订阅者名单”

2. 订阅操作(SUBSCRIBE):登记关注

3. 退订操作(UNSUBSCRIBE):注销关注

三、模式订阅:居民 “按类别订阅所有相关通知栏”(PSUBSCRIBE/PUNSUBSCRIBE)

1. 数据结构对应:社区的 “分类订阅记录”

2. 模式订阅(PSUBSCRIBE):按类别登记

3. 模式退订(PUNSUBSCRIBE):取消分类订阅

四、发布消息:往通知栏贴消息,自动推送给订阅者(PUBLISH)

1. 推送给 “该频道的订阅者”(精准推送)

2. 推送给 “匹配模式的订阅者”(模糊推送)

五、衔接之前的 Redis 结构知识

六、模式订阅的优势:减少重复操作

七、总结:发布订阅的 “社区通知逻辑”


如果把 Redis 的客户端比作 “社区居民”,那发布订阅(Pub/Sub)功能就是社区的 “通知栏 + 分类订阅系统”—— 居民可以 “订阅特定通知栏(频道)” 或 “按类别订阅通知(模式)”,发布者往通知栏贴消息(发布)后,所有订阅的居民都会收到消息。这种机制不用居民主动查消息,实现 “消息主动推送”,就像社区通知自动送到订阅者手里。

一、核心概念:社区的 “通知栏、订阅者与分类”

先理清发布订阅的 3 个核心角色,对应社区场景:

  • 频道(Channel):社区的 “专属通知栏”,比如 “快递 - 北门”“物业 - 缴费”“邻里 - 二手闲置”—— 每个通知栏只发对应类别的消息。

  • 订阅者(Subscriber):关注通知栏的居民,比如老张订阅 “快递 - 北门”,每次有快递通知都会收到。

  • 模式(Pattern):“通知类别模糊匹配”,比如老李订阅 “快递 -*”(* 是通配符),不管是 “快递 - 北门”“快递 - 南门”,只要带 “快递 -” 的通知栏发消息,老李都能收到。

二、频道订阅:居民 “登记关注通知栏”(SUBSCRIBE/UNSUBSCRIBE)

居民要关注某个通知栏,需要 “登记信息”;不想关注了,就 “注销登记”。

Redis 用 pubsub_channels字典  管理这种 “通知栏 - 订阅者” 关系:

1. 数据结构对应:社区的 “通知栏 - 订阅者名单”

Redis 服务器状态里的pubsub_channels是个字典,键是 “频道名(通知栏名)”,值是 “订阅该频道的客户端链表(关注通知栏的居民名单)”:

struct redisServer {dict *pubsub_channels; // 键:频道名,值:订阅者客户端链表
};
  • 比如 “快递 - 北门” 频道的键对应一个链表,里面存着订阅该频道的 3 个客户端(老张、老王、小李)。

2. 订阅操作(SUBSCRIBE):登记关注

居民老张执行SUBSCRIBE 快递-北门,相当于 “在‘快递 - 北门’通知栏的订阅名单上登记自己”,Redis 的操作分两种情况:

  • 通知栏已存在(已有其他人订阅):直接把老张的客户端(redisClient)加到 “快递 - 北门” 对应的链表末尾。
  • 通知栏不存在(没人订阅过):先在pubsub_channels字典里新建 “快递 - 北门” 键,值设为空链表,再把老张的客户端加进去(成为第一个订阅者)。

3. 退订操作(UNSUBSCRIBE):注销关注

老张不想关注 “快递 - 北门” 了,执行UNSUBSCRIBE 快递-北门,Redis 的操作:

  • pubsub_channels字典找到 “快递 - 北门” 对应的链表,删除老张的客户端。
  • 如果删除后链表空了(没人关注这个通知栏了),就把 “快递 - 北门” 这个键从字典里删掉 —— 就像社区撤掉没人关注的通知栏。

三、模式订阅:居民 “按类别订阅所有相关通知栏”(PSUBSCRIBE/PUNSUBSCRIBE)

如果居民老李想关注 “所有快递相关的通知栏”(不管北门、南门),不用逐个登记,只需 “按类别订阅”。Redis 用 pubsub_patterns链表管理这种 “模式 - 订阅者” 关系:

1. 数据结构对应:社区的 “分类订阅记录”

Redis 服务器状态里的pubsub_patterns是个链表,每个节点是pubsubPattern结构,记录 “订阅者(居民)” 和 “订阅的模式(类别)”:

struct redisServer {list *pubsub_patterns; // 存储所有模式订阅关系的链表
};typedef struct pubsubPattern {redisClient *client; // 订阅模式的客户端(老李)robj *pattern;       // 订阅的模式("快递-*")
} pubsubPattern;
  • 比如老李订阅 “快递 -”,链表会新增一个节点:client指向老李的客户端,pattern是 “快递 -” 的字符串对象(底层用 SDS 存储,衔接之前的 SDS 知识)。

2. 模式订阅(PSUBSCRIBE):按类别登记

老李执行PSUBSCRIBE 快递-*,Redis 的操作:

  1. 新建一个pubsubPattern结构,client设为老李的客户端,pattern设为 “快递 -*”(SDS 字符串)。
  2. 把这个结构加到pubsub_patterns链表的末尾 —— 相当于社区记录 “老李要收所有快递相关的通知”。

3. 模式退订(PUNSUBSCRIBE):取消分类订阅

老李不想关注快递类通知了,执行PUNSUBSCRIBE 快递-*,Redis 的操作:

  • 遍历pubsub_patterns链表,找到 “client是老李、pattern是‘快递 -*’” 的节点,删除该节点 —— 相当于社区删掉老李的分类订阅记录。

四、发布消息:往通知栏贴消息,自动推送给订阅者(PUBLISH)

社区管理员(发布者客户端)往 “快递 - 北门” 通知栏贴一条消息 “15:00 有京东快递”,所有相关订阅者都会收到。Redis 执行PUBLISH 快递-北门 "15:00有京东快递"时,分两步推送:

1. 推送给 “该频道的订阅者”(精准推送)

  • Redis 从pubsub_channels字典找到 “快递 - 北门” 对应的链表(里面有老张、老王、小李),把消息逐个发给这三个客户端 —— 就像管理员把通知念给关注 “快递 - 北门” 的居民听。

2. 推送给 “匹配模式的订阅者”(模糊推送)

  • Redis 遍历pubsub_patterns链表,检查每个模式是否匹配 “快递 - 北门”:
    • 老李订阅的 “快递 -*”,*匹配任意字符,所以 “快递 - 北门” 符合模式。
  • 把消息发给老李的客户端 —— 就像管理员把快递通知也发给 “关注所有快递类” 的老李。

五、衔接之前的 Redis 结构知识

发布订阅功能完全复用了之前学的核心结构,体现 Redis 的 “结构复用” 设计:

  1. SDS:频道名、模式名(如 “快递 - 北门”“快递 -*”)都是字符串,底层用 SDS 存储 —— 利用 SDS 的动态扩展、二进制安全特性,支持长名称或特殊字符。
  2. redisClient:订阅者本质是客户端,用redisClient结构体记录(如fdname等属性)—— 和之前讲的 “客户端管理” 逻辑一致。
  3. dict 与 listpubsub_channels用字典(dict)实现 “频道 - 订阅者” 的快速查找(O (1) 定位频道);pubsub_patterns用链表(list)实现 “模式 - 订阅者” 的遍历 —— 复用了字典的快速查找和链表的灵活遍历特性。

六、模式订阅的优势:减少重复操作

如果没有模式订阅,老李要关注 “快递 - 北门”“快递 - 南门”“快递 - 东门” 3 个频道,需要执行 3 次SUBSCRIBE,还要维护 3 个订阅关系;有了模式订阅,只需 1 次PSUBSCRIBE 快递-*,后续新增 “快递 - 东门” 频道,老李不用再操作,自动收到该频道的消息 —— 极大减少重复操作,适合 “批量订阅同类频道” 的场景。

七、总结:发布订阅的 “社区通知逻辑”

Redis 的发布订阅就像社区的智能通知系统:

  • 频道订阅是 “精准关注单个通知栏”,用字典快速定位订阅者;
  • 模式订阅是 “模糊关注一类通知栏”,用链表遍历匹配订阅者;
  • 发布消息是 “贴通知 + 自动推送”,先精准推给频道订阅者,再模糊推给模式订阅者。

这种设计不用订阅者主动轮询消息,实现 “消息实时推送”,同时复用 Redis 的核心数据结构(SDS、dict、list、redisClient),保证效率和灵活性 —— 就像社区通知系统既精准又智能,满足不同居民的订阅需求。

http://www.xdnf.cn/news/1463059.html

相关文章:

  • Linux/UNIX系统编程手册笔记:DAEMON、编写安全的特权程序、能力、登录记账
  • 【QT 5.12.12 下载 Windows 版本】
  • Bing 搜索引擎检索语法
  • CodeQL(Mac)安装与测试(Visual Studio)简明指南
  • 解决IntelliJ IDEA 提交代码时无复选框问题
  • Node.js 做 Web 后端优势为什么这么大?
  • Spark面试题及详细答案100道(56-70)-- 性能优化
  • 逆天!影响因子0.1,竟然划分到中科院1区TOP?
  • 少儿舞蹈小程序(8)校区信息后台搭建
  • linux缺页中断频繁怎么定位
  • flask的使用
  • 栈:简化路径
  • 手写MyBatis第51弹:深入解析MyBatis分页插件原理与手写实现
  • 改 TDengine 数据库的时间写入限制
  • Bug 排查日记:打造高效问题定位与解决的技术秘籍
  • GCC编译器深度解剖:从源码到可执行文件的全面探索
  • 残差连接与归一化结合应用
  • 解决网络太慢问题
  • C++《C++11》(上)
  • 基于单片机智能热水壶/养生壶设计
  • 用 epoll 实现的 Reactor 模式详解(含代码逐块讲解)
  • Vue3源码reactivity响应式篇之EffectScope
  • Android 应用进程启动
  • 趣味学RUST基础篇(构建一个命令行程序2重构)
  • 基于FPGA实现数字QAM调制系统
  • AiPPT生成的PPT内容质量怎么样?会不会出现逻辑混乱或数据错误?
  • 一键生成PPT的AI工具排名:2025年能读懂你思路的AI演示工具
  • 深度学习——迁移学习
  • 鸿蒙:获取UIContext实例的方法
  • Spring Boot+Nacos+MySQL微服务问题排查指南