redis-05-redis发布订阅功能

一、pubsub底层数据结构

Redis中的发布和订阅功能允许服务器向指定的频道发送消息,以及客户端可以订阅感兴趣的频道来接收消息。发布和订阅功能的实现主要由如下几个命令实现:
publish :用于服务器向指定的频道发送消息,格式为:publish channel message
subscribe:用于客户端订阅服务器指定具体名字的频道,格式为: subscribe channel_name
psubscribe:用于客户端订阅服务器指定匹配模式的频道,格式为:psubscribe channel_apttern。
实际上订阅服务器指的匹配模式,可以简单看成 channel_apttern对channel_name的模糊匹配,比如:订阅以 news_开头的channel。如果我向news_sports、news_musice、news_book这几个channel中发送消息,那么订阅了news_的客户端 就是收到这3个 channel的消息。
发布和订阅功能相关的状态都保存在RedisServer中的pubsub_channels和pattern两个字段中:

struct redisServer{
 	// ...  
    dict *pubsub_channels; // 保存所有订阅的频道关系
    list *pattern;         // 保存所有订阅的频道的匹配模式  
    // ....
}

其中dict类型的pubsub_channels保存所有订阅的频道关系,key就是对应的频道名,value就是所有订阅该频道的客户端。由于订阅频道的客户端可能有多个,这里采用了链表的形式进行保存。如下所示:
这种结构类似于hashMap。
在这里插入图片描述

1、channel的订阅与退订

当客户端发不一个不存的channel中发送服务时,会返回0。说明这个channel不存在

10.0.1.1:6379> publish "test_tttt" "testtet"
(integer) 0

当客户端订阅某个频道时,如果pubsub_channels中已经有了该频道名,说明该频道已经有订阅者,将其直接添加到订阅者链表的尾部即可。如果pubsub_channels并没有指定的频道名,需要先将频道添加到pubsub_channels中,然后该客户端作为链表的头节点存在。
向已经有客户端订阅的channel中发送消息,当消息发送成功后,返回1。

10.0.1.1:6379> publish "news_it" "redis good"
(integer) 1

退订频道和订阅频道的动作相反,如果某个客户端想要退订某个频道,服务器会遍历pubsub_channels,找到对应频道的订阅者链表,将其从链表中删除。此外,如果该客户端是这个频道的唯一订阅者,删除订阅者后,还需要将该频道从pubsub_channels中删除。

2、模式订阅与退订

模式的订阅和退订实现与频道的订阅和退订实现类似,RedisServer的pubsub_patterns维护一个由pubsubPattern项组成的链表,其中pubsubPattern又包含client和pattern两个属性,用于表示具体的客户端和订阅的模式。

struct pubsubPattern{
	redisClient client;
    robj *pattern;
}

在这里插入图片描述

当客户端订阅某个模式时,服务器会创建一个pubsubPattern结构,将其pattern属性设置为被订阅模式,最后将其添加到链表的尾部。如果客户端退订某个模式时,服务器会在链表中找到对应的pubsubPattern结构,并删除这些结构。

3、向channel发送消息

当服务器对某个频道发布消息时,需要执行如下的两个动作:
在pubsub_channels中找到具体的频道,向订阅者列表中的每个客户端发送消息
遍历pubsub_patterns找到匹配的模式,然后向对应的客户端发送消息
Redis提供了PUBSUB命令来用于服务器获取订阅信息,其中又分为如下的三个子命令:

获取服务器当前被订阅的频道,格式为:PUBSUB CHANNELS [pattern]
返回指定频道的订阅者数量,格式为:PUBSUB NUMSUB [channel-1 channel-2 ... channel-n]
获取服务器当前被订阅模式的数量,格式为:PUBSUB NUMPAT

如果所有客户端与redis断开连接,那么客户端订阅的这个channel就会被删除,相当于所有客户端退订了channel。
获取所有channels

10.0.0.1:6379> pubsub channels
 1) "news_11"
 2) "news_22"
 3) "news_33"
 4) "musics_11"

获取模式cache的channels

10.0.0.1:6379> pubsub channels cache*
1) "cache:11"
2) "cache:ma:1"
3) "cache:bill:2"
4) "cache:customer:3"
5) "cache:ma:5"
6) "cache:999"

获这个channel所有订阅者数量

10.0.0.1:6379> pubsub numsub cache:11
1) "cache:11"
2) (integer) 3

二、发布订阅中的一些其它注意

1、一些需要注意的命令

1、如果向一个不存在的channel中发送消息,那么会返回0,说明这个channel不存在,发送消息失败。没有客户端订阅这个channel。
2、当所有客户端断开redis连接,那么就相当于退订阅了它所订阅的channel。所有之前订阅了一个channel的客户端与redis断开连接,那么相当于所有客户端退订了这个channel,这个channel就会被删除。
3、新订阅的客户端,是无法收到这个频道之前的消息,这是因为 Redis 并不会对发布的消息持久化的。

相比于很多专业 MQ,比如 kafka、rocketmq 来说, redis 发布订阅功能就显得有点简陋了。不过 redis 发布订阅功能胜在简单,如果当前场景可以容忍这些缺点,还是可以选择使用的。

2、原生jedis.subscribe 是一个阻塞的方法

不过需要注意的是,jedis#subscribe 是一个阻塞方法,调用之后将会阻塞主线程的,所以如果需要在正式项目使用需要使用异步线程运行,这里就不演示具体的代码了。
可基于基于 Spring-Data-Redis 开发发布订阅

三、redis发布订阅的应用

1、当订单支付成功后

使用 Redis 发布订阅这种机制,对于上面业务,下单支付业务只需要向支付结果这个频道发送消息,其他下游业务订阅支付结果这个频道,就能收相应消息,然后做出业务处理即可。

这样就可以解耦系统上下游之间调用关系。

1、Redis Sentinel 节点发现

Redis Sentinel 是 Redis 一套高可用方案,可以在主节点故障的时候,自动将从节点提升为主节点,从而转移故障。
Redis Sentinel 节点主要使用发布订阅机制,实现新节点的发现,以及交换主节点的之间的状态。

如下所示,每一个 Sentinel 节点将会定时向 sentinel:hello 频道发送消息,并且每个 Sentinel 都会订阅这个节点。

在这里插入图片描述
每次往这个频道发送消息内容可以包含节点的状态信息,这样可以作为后面 Sentinel 领导者选举的依据。
通过订阅实例的HELLO频道,接收其他哨兵通过”PUBLISH”命令发布的信息,从而得到监控同一主节点的所有其他哨兵的信息。
以上都是对于 Redis 服务端来讲,对于客户端来讲,我们也可以用到发布订阅机制。

2、当sentinel完成故障转移,选出新master时通知各个客户端

对于我们客户端来讲,比较关心切换之后的主节点,这样我们及时切换主节点的连接(旧节点此时已故障,不能再接受操作指令),

客户端可以订阅 +switch-master频道,一旦 Redis Sentinel 结束了对主节点的故障转移就会发布主节点的的消息。

3、redission 分布式锁用了发布订阅

今天我们来看下 Redis 的实现分布式锁中如何使用 Redis 发布订阅机制,提高加锁的性能。
首先我们来看下 redission 加锁的方法:

RedissonLock redisson = ...
RLock redissonLock = redisson.getLock("xxx");
redisson.lock();

RLock 继承自 Java 标准的 Lock接口 ,调用 lock 方法,如果当前锁已被其他客户端获取,那么当前加锁的线程将会被阻塞,直到其他客户端释放这把锁。

3.1、其它客户端如何感知锁被释放

这里其实有个问题,当前阻塞的线程如何感知分布式锁已被释放呢?
这里其实有两种实现方法:

第一种:定时查询分布式锁的状态,一旦查到锁已被释放(redis中不存在这个键值),那么就去加锁。实现伪代码如下:
 while (true){
   boolean result = lock();
    if(!result){
        Thread.sleep(N);
    }
}

这种方式实现起来起来简单,不过缺点也比较多。

如果定时任务时间过短,将会导致查询次数过多,其实这些都是无效查询。

如果定时任务休眠时间过长,那又会导致加锁时间过长,导致加锁性能不好。

第二种实现方案,就是采用服务通知的机制,当分布式锁被释放之后,客户端可以收到锁释放的消息,然后第一时间再去加锁。

这个服务通知的机制我们可以使用 Redis 发布订阅模式。
当线程加锁失败之后,线程将会订阅 redisson_lock__channel_xxx(xx 代表锁的名称) 频道,使用异步线程监听消息,然后利用 Java 中 Semaphore 使当前线程进入阻塞。
一旦其他客户端进行解锁,redission 就会往这个redisson_lock__channel_xxx 发送解锁消息。
等异步线程收到消息,将会调用 Semaphore 释放信号量,从而让当前被阻塞的线程唤醒去加锁。

通过发布订阅机制,被阻塞的线程可以及时被唤醒,减少无效的空转的查询,有效的提高的加锁的效率。

redisson获锁源码查看另一篇文章Redisson框架实现Redis分布式锁的实现原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
概要介绍: 本课程主要是介绍并实战一款java中间件~redisson,介绍redisson相关的核心技术栈及其典型的应用场景,其中的应用场景就包括布隆过滤器、限流器、短信发送、实时/定时邮件发送、数据字典、分布式服务调度等等,在业界号称是在java项目里正确使用redis的姿势。本课程的目标就在于带领各位小伙伴一起学习、攻克redisson,更好地巩固自己的核心竞争力,而至于跳槽涨薪,自然不在话下!  课程内容: 说起redisson,可能大伙儿不是很熟悉,但如果说起redis,想必肯定很多人都晓得。没错,这家伙字如其名,它就是架设在redis基础上的一款综合性的、新型的中间件,号称是java企业级应用开发中正确使用redis的姿势/客户端实例。 它是架设在redis基础之上,但拥有的功能却远远多于原生Redis 所提供的,比如分布式对象、分布式集合体系、分布式锁以及分布式服务调度等一系列具有分布式特性的对象实例… 而这些东西debug将在本门课程进行淋漓尽致的介绍并实战,除此之外,我们将基于spring boot2.0搭建的多模块项目实战典型的应用场景:对象存储、数据字典、短信发送、实时/定时邮件发送、布隆过滤器、限流组件、分布式服务调度....课程大纲如下所示: 下面罗列一下比较典型的核心技术栈及其实际业务场景的实战,如下图所示为redisson基于订阅-发布模式的核心技术~主题Topic的实际业务场景,即实时发送邮件: 而下图则是基于“多值映射MultiMap”数据结构实战实现的关于“数据字典”的缓存管理: 除此之外,我们还讲解了可以与分布式服务调度中间件dubbo相媲美的功能:分布式远程服务调度,在课程中我们动手搭建了两个项目,用于分别充当“生产者”与“消费者”角色,最终通过redisson的“服务调度组件”实现服务与服务之间、接口与接口之间的调用!  课程收益: (1)认识并掌握redisson为何物、常见的几种典型数据结构-分布式对象、集合、服务的应用及其典型应用场景的实战; (2)掌握如何基于spring boot2.0整合redisson搭建企业级多模块项目,并以此为奠基,实战企业级应用系统中常见的业务场景,巩固相应的技术栈! (3)站在项目管理与技术精进的角度,掌握对于给定的功能模块进行业务流程图的绘制、分析、模块划分、代码实战与性能测试和改进,提高编码能力与其他软实力; (4)对于Java微服务、分布式、springboot精进者而言,学完本课程,不仅可以巩固提高中间件的实战能力,其典型的应用场景更有助于面试、助力相关知识点的扫盲! 如下图所示: 关键字:Spring Boot,Redis,缓存穿透,缓存击穿,缓存雪崩,红包系统,Mybatis,高并发,多线程并发编程,发送邮件,列表List,集合Set,排行榜,有序集合SortedSet,哈希Hash ,进阶实战,面试,微服务、分布式 适用人群:redisson学习者,分布式中间件实战者,微服务学习者,java学习者,spring boot进阶实战者,redis进阶实战者

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值