什么是发布订阅?

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis 的 subscribe 命令可以让客户端订阅任意数量的频道, 每当有新信息发送到被订阅的频道时, 信息就会被发送给所有订阅指定频道的客户端。

☛ 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

☛ 当有新消息通过 publish 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

☆ 为什么要用发布订阅?

熟悉消息中间件的同学都知道,针对消息订阅发布功能,市面上很多大厂使用的是kafkaRabbitMQActiveMQRocketMQ等这几种,redis的订阅发布功能跟这三者相比,相对轻量,针对数据准确和安全性要求没有那么高可以直接使用,适用于小公司。

redis 的List数据类型结构提供了 blpop 、brpop 命令结合 rpush、lpush 命令可以实现消息队列机制,基于双端链表实现的发布与订阅功能

这种方式存在两个局限性:

  • 不能支持一对多的消息分发。
  • 如果生产者生成的速度远远大于消费者消费的速度,易堆积大量未消费的消息

◇ 双端队列图解如下:

解析:双端队列模式只能有一个或多个消费者轮着去消费,却不能将消息同时发给其他消费者

◇ 发布/订阅模式图解如下:

解析:redis订阅发布模式,生产者生产完消息通过频道分发消息,给订阅了该频道的所有消费

发布/订阅如何使用?

Redis有两种发布/订阅模式:

  • 基于频道(Channel)的发布/订阅
  • 基于模式(pattern)的发布/订阅

操作命令如下

序号 命令与描述

● 基于频道
1

subscribe channel  [channel ... ]

订阅给定的一个或多个频道

2

unsubscribe  channel  [channel ... ]

退订给定的频道

说明:若没有指定channel,则默认退订所有频道

3

publish channel message 

将消息发送给指定频道 channel

返回结果:接收到信息的订阅者数量,无订阅者返回0

4

pubsub channels [argument  [atgument ...] ]

查看订阅与发布系统的状态

说明:返回活跃频道列表(即至少有一个订阅者的频道,订阅模式的客户端除外)

● 基于模式

5

psubscribe pattern1  [pattern...]

订阅一个或多个符合给定模式的频道

说明:每个模式以 * 作为匹配符;例如 cn* 匹配所有以cn开头的频道:cn.java、cn.csdn

6

punsubscribe [pattern [pattern ...] ] 

退订所有给定模式的频道

说明:pattern 未指定,则订阅的所有模式都会被退订,否则只退订指定的订阅的模式

基于频道(Channel)的发布/订阅

"发布/订阅" 包含2种角色:发布者和订阅者。发布者可以向指定的频道(channel)发送消息;订阅者可以订阅一个或者多个频道(channel),所有订阅此频道的订阅者都会收到此消息。

  • 订阅者订阅频道   subscribe channel  [channel ...]
--------------------------客户端1(订阅者) :订阅频道 ---------------------# 订阅 “meihuashisan” 和 “csdn” 频道(如果不存在则会创建频道)
127.0.0.1:6379> subscribe meihuashisan csdn
Reading messages... (press Ctrl-C to quit)1) "subscribe"    -- 返回值类型:表示订阅成功!
2) "meihuashisan" -- 订阅频道的名称
3) (integer) 1    -- 当前客户端已订阅频道的数量1) "subscribe"
2) "csdn"
3) (integer) 2#注意:订阅后,该客户端会一直监听消息,如果发送者有消息发给频道,这里会立刻接收到消息
  • 发布者发布消息   publish channel message
--------------------------客户端2(发布者):发布消息给频道 -------------------# 给“meihuashisan”这个频道 发送一条消息:“I am meihuashisan”
127.0.0.1:6379> publish meihuashisan "I am meihuashisan"
(integer) 1  # 接收到信息的订阅者数量,无订阅者返回0

客户端2 (发布者) 发布消息给频道后,此时我们再来观察 客户端1 (订阅者) 的客户端窗口变化:

# --------------------------客户端1(订阅者) :订阅频道 -----------------127.0.0.1:6379> subscribe meihuashisan csdn
Reading messages... (press Ctrl-C to quit)1) "subscribe"    -- 返回值类型:表示订阅成功!
2) "meihuashisan" -- 订阅频道的名称
3) (integer) 1    -- 当前客户端已订阅频道的数量1) "subscribe"
2) "csdn"
3) (integer) 2---------------------变化如下:(实时接收到了该频道的发布者的消息)------------1) "message"           -- 返回值类型:消息
2) "meihuashisan"      -- 来源(从哪个频道发过来的)
3) "I am meihuashisan" -- 消息内容

命令操作图解如下:

注意:如果是先发布消息,再订阅频道,不会收到订阅之前就发布到该频道的消息!

注意:进入订阅状态的客户端,不能使用除了subscribeunsubscribepsubscribe 和 punsubscribe 这四个属于"发布/订阅"之外的命令,否则会报错!

——这里的客户端指的是 jedis、lettuce的客户端,redis-cli是无法退出订阅状态的!

★ 实现原理

底层通过字典实现。pubsub_channels 是一个字典类型,保存订阅频道的信息:字典的key为订阅的频道, 字典的value是一个链表, 链表中保存了所有订阅该频道的客户端

struct redisServer { /* General */ pid_t pid; //省略百十行 // 将频道映射到已订阅客户端的列表(就是保存客户端和订阅的频道信息)dict *pubsub_channels; /* Map channels to list of subscribed clients */
}

 实现图如下: 

频道订阅:订阅频道时先检查字段内部是否存在;不存在则为当前频道创建一个字典且创建一个链表存储客户端id;否则直接将客户端id插入到链表中。

取消频道订阅:取消时将客户端id从对应的链表中删除;如果删除之后链表已经是空链表了,则将会把这个频道从字典中删除。

发布:首先根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端

基于模式(pattern)的发布/订阅

如果有某个/某些模式和该频道匹配,所有订阅这个/这些频道的客户端也同样会收到信息。

图解

下图展示了一个带有频道和模式的例子, 其中 com.ahead.* 频道匹配了 com.ahead.juc 频道和 com.ahead.thread 频道, 并且有不同的客户端分别订阅它们三个,如下图:

当有信息发送到com.ahead.thread 频道时, 信息除了发送给 client 4client 5 之外, 还会发送给订阅 com.ahead.*  频道模式的 client xclient y

解析:反之也是,如果当有消息发送给 com.ahead.juc 频道,消息发送给订阅了 juc 频道的客户端之外,还会发送给订阅了 com.ahead.*  频道的客户端: client x 、client y

通配符中?表示1个占位符,*表示任意个占位符(包括0),?*表示1个以上占位符。

  •  订阅者订阅频道   psubscribe pattern  [pattern ...]
--------------------------客户端1(订阅者) :订阅频道 ---------------------#  1. ------------订阅 “a?” "com.*" 2种模式频道--------------
127.0.0.1:6379> psubscribe a? com.*
# 进入订阅状态后处于阻塞,可以按Ctrl+C键退出订阅状态
Reading messages... (press Ctrl-C to quit) # 2. ---------------订阅成功-------------------1) "psubscribe"  -- 返回值的类型:显示订阅成功
2) "a?"          -- 订阅的模式
3) (integer) 1   -- 目前已订阅的模式的数量1) "psubscribe"
2) "com.*"
3) (integer) 2# 3. ---------------接收消息 (已订阅 “a?” "com.*" 两种模式!)-----------------# ---- 发布者第1条命令: publish ahead "hello"
结果:没有接收到消息,匹配失败,不满足 “a?” ,“?”表示一个占位符, a后面的head有4个占位符# ---- 发布者第2条命令:  publish aa "hello" (满足 “a?”)
1) "pmessage" -- 返回值的类型:信息
2) "a?"       -- 信息匹配的模式:a?
3) "aa"       -- 信息本身的目标频道:aa
4) "hello"    -- 信息的内容:"hello"# ---- 发布者第3条命令:publish com.juc "hello2"(满足 “com.*”, *表示任意个占位符)
1) "pmessage" -- 返回值的类型:信息
2) "com.*"    -- 匹配模式:com.*
3) "com.juc"  -- 实际频道:com.juc
4) "hello2"   -- 信息:"hello2"# ---- 发布者第4条命令: publish com. "hello3"(满足 “com.*”, *表示任意个占位符)
1) "pmessage" -- 返回值的类型:信息
2) "com.*"    -- 匹配模式:com.*
3) "com."     -- 实际频道:com.
4) "hello3"   -- 信息:"hello3"
  • 发布者发布消息   publish channel message
--------------------------客户端2(发布者):发布消息给频道 -------------------注意:订阅者已订阅 “a?” "com.*" 两种模式!# 1. ahead 不符合“a?”模式,?表示1个占位符
127.0.0.1:6379> publish ahead "hello"
(integer) 0    -- 匹配失败,0:无订阅者# 2. aa 符合“a?”模式,?表示1个占位符
127.0.0.1:6379> publish aa "hello"
(integer) 1# 3. 符合“com.*”模式,*表示任意个占位符
127.0.0.1:6379> publish com.juc "hello2"
(integer) 1# 4. 符合“com.*”模式,*表示任意个占位符
127.0.0.1:6379> publish com. "hello3"
(integer) 1

命令操作图解如下:

★ 实现原理

底层是pubsubPattern节点的链表

struct redisServer {//...list *pubsub_patterns; // ...
}// 1303行订阅模式列表结构:
typedef struct pubsubPattern {client *client;  -- 订阅模式客户端robj *pattern;   -- 被订阅的模式
} pubsubPattern;

实现图如下:

模式订阅:新增一个pubsub_pattern数据结构添加到链表的最后尾部,同时保存客户端ID

取消模式订阅:从当前的链表pubsub_pattern结构中删除需要取消的pubsubPattern结构。

使用小结

订阅者(listener)负责订阅频道(channel);发送者(publisher)负责向频道发送二进制的字符串消息,然后频道收到消息时,推送给订阅者。

✦ 使用场景

  • 电商中,用户下单成功之后向指定频道发送消息,下游业务订阅支付结果这个频道处理自己相关业务逻辑
  • 粉丝关注功能
  • 文章推送

✦ 使用注意

Redis进阶——发布订阅详解相关推荐

  1. Redis进阶-发布订阅简介

    1. Redis 发布订阅简介 Redis 发布订阅(pub/sub)是一种消息通信模式: 发送者(pub):发送消息 订阅者(sub):接收消息 Redis的 subscribe命令可以让客户端订阅 ...

  2. Redis进阶-bind参数详解

    文章目录 redis.conf 验证bind bind 127.0.0.1 bind 192.168.18.130 bind 192.168.18.130 127.0.0.1 redis.conf R ...

  3. Redis:发布订阅机制

    参考资料: <Redis进阶--发布订阅详解> <Redis 发布订阅> <Redis进阶 - 消息传递:发布订阅模式详解> 写在开头:本文为学习后的总结,可能有不 ...

  4. [redis] 10 种数据结构详解

    [redis] 10 种数据结构详解 简介 5种常见数据结构 string: 最常见的 string key value list: 双向链表 set: 集合- zset: 有序集合 hash: 类似 ...

  5. Redis设计与实现详解二:Redis数据库实现

    Redis设计与实现详解一:数据结构与对象 Redis设计与实现详解三:多机功能实现 Redis设计与实现详解四:其他单机功能 数据库 服务器中的数据库 Redis服务器将所有数据库都保存在服务器状态 ...

  6. Redis基础及原理详解

    Redis基础及原理详解 前言:以下是最近学习redis的一些笔记总结,文中如有不当的地方欢迎批评指正,重在记录与学习,笔芯~~ Nosql概述 演进历史 单机mysql Memcached(缓存)+ ...

  7. Spring boot整合Redis实现发布订阅(超详细)

    Redis发布订阅 基础知识 相关命令 订阅者/等待接收消息 发布者/发送消息 订阅者/成功接收消息 常用命令汇总 原理 Spring boot整合redis 导入依赖 Redis配置 消息封装类(M ...

  8. 【springboot】【redis】springboot+redis实现发布订阅功能,实现redis的消息队列的功能...

    springboot+redis实现发布订阅功能,实现redis的消息队列的功能 参考:https://www.cnblogs.com/cx987514451/p/9529611.html 思考一个问 ...

  9. Redis数据库教程——系统详解学习Redis全过程

    Redis数据库教程--系统详解学习Redis全过程 Redis快速入门:Key-Value存储系统简介 Key-Value存储系统:     Key-Value Store是当下比较流行的话题,尤其 ...

最新文章

  1. 这个项目团队能少了谁?
  2. python 没找到库_这十个Python常用库,学习Python的你必须要知道!
  3. Goalng软件包推荐
  4. C语言 BMP24位变单色,怎么将24位色BMP图片改为单色或16色?(2)
  5. 机器学习week8 ex7 review
  6. spring依赖注入的基本方法及其原理
  7. 线性表的顺序表示和实现(严蔚敏版)
  8. 同一目录下有大量文件会影响效率吗_成考学习效率太低?可以从这7方面备考...
  9. 破解qq上网限制 突破限制上QQ
  10. SpringBoot AOP注解写法
  11. photoshop抠图与相框的制作
  12. info There appears to be trouble with your network connection. Retrying...
  13. 系统地学习3D建模!教你零基础入门
  14. 广度优先搜索(BFS)与深度优先搜索(DFS)的对比及优缺点
  15. .net基础知识学习
  16. 科研萌新成长记8——我不是不接受上帝,我只是不接受上帝创造的这个世界
  17. JavaScript常见面试题
  18. 电影《肖申克的救赎》给你最深的感受是什么?
  19. 小程序创业会有哪些坑
  20. matlab gain使用,matlabgain模块

热门文章

  1. 双目立体标定与极线校正【双目立体视觉几何】
  2. 做好软件测试才能提升应用质量
  3. 问 0x7FFFFFFF+0x7FFFFFFF 是多少
  4. 【0x7FFFFFFF】【0x3f3f3f3f】
  5. 频率学派(似然估计)与贝叶斯学派(后验估计)
  6. Python单线程爬取QQ空间说说存入MySQL并生成词云(超详细讲解,踩坑经历)
  7. Windows-如何查看域用户的最终密码更改日期等详细信息
  8. 技术讨论:我心中TOP1的编程语言
  9. [5G][NR] PDSCH DMRS
  10. 致那逝去的青春,展望未来