Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Redis 发布订阅,小功能大用处,真没那么废材!

Redis 发布订阅,小功能大用处,真没那么废材!

作者头像
andyxh
发布于 2020-09-24 07:13:51
发布于 2020-09-24 07:13:51
61600
代码可运行
举报
文章被收录于专栏:程序通事程序通事
运行总次数:0
代码可运行

今天小黑哥来跟大家介绍一下 Redis 发布/订阅功能。

也许有的小伙伴对这个功能比较陌生,不太清楚这个功能是干什么的,没关系小黑哥先来举个例子。

假设我们有这么一个业务场景,在网站下单支付以后,需要通知库存服务进行发货处理。

上面业务实现不难,我们只要让库存服务提供给相关的给口,下单支付之后只要调用库存服务即可。

后面如果又有新的业务,比如说积分服务,他需要获取下单支付的结果,然后增加用户的积分。

这个实现也不难,让积分服务同样提供一个接口,下单支付之后只要调用库存服务即可。

如果就两个业务需要获取下单支付的结果,那也还好,程序改造也快。可是随着业务不断的发展,越来越多的新业务说是要下单支付的结果。

这时我们会发现上面这样的系统架构存在很多问题:

第一,下单支付业务与其他业务重度耦合,每当有个新业务需要支付结果,就需要改动下单支付的业务。

第二,如果调用业务过多,会导致下单支付接口响应时间变长。另外,如果有任一下游接口响应变慢,就会同步导致下单支付接口响应也变长。

第三,如果任一下游接口失败,可能导致数据不一致的情况。比如说下图,先调用 A,成功之后再调用 B,最后再调用 C。

如果在调用 B 接口的发生异常,此时可能就导致下单支付接口返回失败,但是此时 A 接口其实已经调用成功,这就代表它内部已经处理下单支付成功的结果。

这样就会导致 A,B,C 三个下游接口,A 获取成功获取支付结果,但是 B,C 没有拿到,导致三者系统数据不一致的情况。

其实我们仔细想一下,对于下单支付业务来讲,它其实不需要关心下游调用结果,只要有某种机制通知能通知到他们就可以了。

讲到这里,这就需要引入今天需要介绍发布订阅机制。

Redis 发布与订阅

Redis 提供了基于「发布/订阅」模式的消息机制,在这种模式下,消息发布者与订阅者不需要进行直接通信。

如上图所示,消息发布者只需要想指定的频道发布消息,订阅该频道的每个客户端都可以接受到到这个消息。

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

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

接下来我们来看下,我们来看下如何使用 Redis 发布订阅功能。

Redis 中提供了一组命令,可以用于发布消息,订阅频道,取消订阅以及按照模式订阅。

首先我们来看下如何发布一条消息,其实很简单只要使用 publish 指令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
publish channel message

上图中,我们使用 publish 指令向 pay_result 这个频道发送了一条消息。我们可以看到 redis 向我们返回 0 ,这其实代表当前订阅者个数,由于此时没有订阅,所以返回结果为 0 。

接下来我们使用 subscribe 订阅一个或多个频道

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
subscribe channel [channel ...]

如上图所示,我们订阅 pay_result 这个频道,当有其他客户端往这个频道发送消息,

当前订阅者就会收到消息。

我们子在使用订阅命令,需要主要几点:

第一,客户端执行订阅指令之后,就会进入订阅状态,之后就只能接收 subscribepsubscribeunsubscribepunsubscribe 这四个命令。

第二,新订阅的客户端,是无法收到这个频道之前的消息,这是因为 Redis 并不会对发布的消息持久化的。

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

除了上面的功能以外的,Redis 还支持模式匹配的订阅方式。简单来说,客户端可以订阅一个带 * 号的模式,如果某些频道的名字与这个模式匹配,那么当其他客户端发送给消息给这些频道时,订阅这个模式的客户端也将会到收到消息。

使用 Redis 订阅模式,我们需要使用一个新的指令 psubscribe

我们执行下面这个指令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
psubscribe pay.*

那么一旦有其他客户端往 pay 开头的频道,比如 pay_resultpay_xxx,我们都可以收到消息。

如果需要取消订阅模式,我们需要使用相应punsubscribe 指令,比如取消上面订阅的模式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
punsubscribe pay.*

Redis 客户端发布订阅使用方式

基于 Jedis 开发发布/订阅

聊完 Redis 发布订阅指令,我们来看下 Java Redis 客户端如何使用发布订阅。

下面的例子主要基于 Jedis,maven 版本为: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency> 其他 Redis 客户端大同小异。

jedis 发布代码比较简单,只需要调用 Jedis 类的 publish 方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 生产环境千万不要这么使用哦,推荐使用 JedisPool 线程池的方式 
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("xxxxx");
jedis.publish("pay_result", "hello world");

订阅的代码就相对复杂了,我们需要继承 JedisPubSub 实现里面的相关方法,一旦有其他客户端往订阅的频道上发送消息,将会调用 JedisPubSub 相应的方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static class MyListener extends JedisPubSub {
    @Override
    public void onMessage(String channel, String message) {
        System.out.println("收到订阅频道:" + channel + " 消息:" + message);

    }

    @Override
    public void onPMessage(String pattern, String channel, String message) {
        System.out.println("收到具体订阅频道:" + channel + "订阅模式:" + pattern + " 消息:" + message);
    }

}

其次我们需要调用 Jedis 类的 subscribe 方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("xxx");
jedis.subscribe(new MyListener(), "pay_result");

当有其他客户端往 pay_result频道发送消息时,订阅将会收到消息。

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

基于 Spring-Data-Redis 开发发布订阅

原生 jedis 发布订阅操作,相对来说还是有点复杂。现在我们很多应用已经基于 SpringBoot 开发,使用 spring-boot-starter-data-redis ,可以简化发布订阅开发。

首先我们需要引入相应的 startter 依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

这里我们使用 Jedis 当做底层连接客户端,所以需要排除 lettuce,然后引入 Jedis 依赖。

然后我们需要创建一个消息接收类,里面需要有方法消费消息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
public class Receiver {
    private AtomicInteger counter = new AtomicInteger();

    public void receiveMessage(String message) {
        log.info("Received <" + message + ">");
        counter.incrementAndGet();
    }

    public int getCount() {
        return counter.get();
    }
}

接着我们只需要注入 Spring- Redis 相关 Bean,比如:

  • StringRedisTemplate,用来操作 Redis 命令
  • MessageListenerAdapter ,消息监听器,可以在这个类注入我们上面创建消息接受类 Receiver
  • RedisConnectionFactory, 创建 Redis 底层连接
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
public class MessageConfiguration {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        // 订阅指定频道使用 ChannelTopic
        // 订阅模式使用 PatternTopic
        container.addMessageListener(listenerAdapter, new ChannelTopic("pay_result"));

        return container;
    }

    @Bean
    MessageListenerAdapter listenerAdapter(Receiver receiver) {
        // 注入 Receiver,指定类中的接受方法
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    @Bean
    Receiver receiver() {
        return new Receiver();
    }

    @Bean
    StringRedisTemplate template(RedisConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }

}

最后我们使用 StringRedisTemplate#convertAndSend 发送消息,同时 Receiver 将会收到一条消息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@SpringBootApplication
public class MessagingRedisApplication {
    public static void main(String[] args) throws InterruptedException {

        ApplicationContext ctx = SpringApplication.run(MessagingRedisApplication.class, args);

        StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class);
        Receiver receiver = ctx.getBean(Receiver.class);

        while (receiver.getCount() == 0) {
            template.convertAndSend("pay_result", "Hello from Redis!");
            Thread.sleep(500L);
        }

        System.exit(0);
    }
}

Redis 发布订阅实际应用

Redis Sentinel 节点发现

Redis Sentinel 是 Redis 一套高可用方案,可以在主节点故障的时候,自动将从节点提升为主节点,从而转移故障。

今天这里我们不详细解释 Redis Sentinel 详细原理,主要来看下 Redis Sentinel 如何使用发布订阅机制。

Redis Sentinel 节点主要使用发布订阅机制,实现新节点的发现,以及交换主节点的之间的状态。

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

这样一旦有节点往这个频道发送消息,其他节点就可以立刻收到消息。

这样一旦有的新节点加入,它往这个频道发送消息,其他节点收到之后,判断本地列表并没有这个节点,于是就可以当做新的节点加入本地节点列表。

除此之外,每次往这个频道发送消息内容可以包含节点的状态信息,这样可以作为后面 Sentinel 领导者选举的依据。

以上都是对于 Redis 服务端来讲,对于客户端来讲,我们也可以用到发布订阅机制。

Redis Sentinel 进行主节点故障转移,这个过程各个阶段会通过发布订阅对外提供。

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

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

redission 分布式锁

redission 开源框架提供一些便捷操作 Redis 的方法,其中比较出名的 redission 基于 Redis 的实现分布式锁。

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

PS:redission 分布式锁实现原理,可以参考之前写过的文章:

  1. 可重入分布式锁的实现方式
  2. Redis 分布式锁,看似简单,其实真不简单

首先我们来看下 redission 加锁的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Redisson redisson = ....
RLock redissonLock = redisson.getLock("xxxx");
redissonLock.lock();

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

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

这里其实有两种实现方法:

第一钟,定时查询分布时锁的状态,一旦查到锁已被释放(Redis 中不存在这个键值),那么就去加锁。

实现伪码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
while (true) {
  boolean result=lock();
  if (!result) {
    Thread.sleep(N);
  }
}

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

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

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

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

这个服务通知的机制我们可以使用 Redis 发布订阅模式。

当线程加锁失败之后,线程将会订阅 redisson_lock__channel_xxx(xx 代表锁的名称) 频道,使用异步线程监听消息,然后利用 Java 中 Semaphore 使当前线程进入阻塞。

一旦其他客户端进行解锁,redission 就会往这个redisson_lock__channel_xxx 发送解锁消息。

等异步线程收到消息,将会调用 Semaphore 释放信号量,从而让当前被阻塞的线程唤醒去加锁。

ps:这里只是简单描述了 redission 加锁部分原理,出于篇幅,这里就不再消息解析源码。 感兴趣的小伙伴可以自己看下 redission 加锁的源码。

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

ps: 这种方式,性能确实提高,但是实现起来的复杂度也很高,这部分源码有点东西,快看晕了。

总结

今天我们主要介绍 Redis 发布订阅功能,主要对应的 Redis 命令为:

  • subscribe channel [channel ...] 订阅一个或多个频道
  • unsubscribe channel 退订指定频道
  • publish channel message 发送消息
  • psubscribe pattern 订阅指定模式
  • punsubscribe pattern 退订指定模式

我们可以利用 Redis 发布订阅功能,实现的简单 MQ 功能,实现上下游的解耦。

不过需要注意了,由于 Redis 发布的消息不会被持久化,这就会导致新订阅的客户端将不会收到历史消息。

所以,如果当前的业务场景不能容忍这些缺点,那还是用专业 MQ 吧。

最后介绍了两个使用 Redis 发布订阅功能使用场景供大家参考。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis发布订阅:我想着应该是全网讲解最简单最通俗的文章了吧!
可能小伙伴的工作年限大部分已经超过三年甚至四年五年,不知道是否有一种危机感,我们写了那么多的需求代码没有20w行也有个10w行了吧,但是出去找工作的时候不是笔试被pass掉就是面试被pass,你会发现好多你只是知道但是回答不上来。这个时候你才知道去补习知识点,其实这种做法对自身发展不太友好的。
我是阿沐
2021/06/14
1.6K0
Redis发布订阅:我想着应该是全网讲解最简单最通俗的文章了吧!
Springboot中使用Redis的发布/订阅模式
pom.xml 引入redis依赖 application.properties略
heasy3
2020/08/01
1K0
Springboot中使用Redis的发布/订阅模式
Redis 发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
只喝牛奶的杀手
2019/08/26
6520
Redis 发布订阅
我在生产项目里是如何使用Redis发布订阅的?(一)业务场景
Redis是我们很常用的一款nosql数据库产品,我们通常会用Redis来配合关系型数据库一起使用,弥补关系型数据库的不足。
编程大道
2019/07/15
7.3K0
我在生产项目里是如何使用Redis发布订阅的?(一)业务场景
Redis发布订阅模式
Redis自身提供了发布/订阅(publish/subscribe)模式。实现方式大致流程如下图:
ruochen
2021/11/22
9840
Redis:发布(pub)与订阅(sub)实战
Redis发布订阅(Pub/Sub)是Redis提供的一种消息传递机制,它使用“发布者-订阅者”(publisher-subscriber)模式来处理消息传递。在这种模式下,发布者将消息发布到一组订阅者中,而无需关心谁是订阅者,也不需要知道订阅者是否收到了消息。
不惑
2023/11/17
2.2K1
Redis:发布(pub)与订阅(sub)实战
深入浅出Redis(九):Redis的发布订阅模式
Redis是一款基于内存的键值对数据库,提供了多种数据结构存储数据,存取数据的速度还非常快,除了这些优点它还提供了其他特色功能,比如:管道、lua脚本、发布订阅模型
菜菜的后端私房菜
2024/09/19
1700
【Redis】010-Redis发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统;
訾博ZiBo
2025/01/06
1070
【Redis】010-Redis发布订阅
Redis总结之发布订阅
实践环节,大家注意到小编是先开启的订阅者客户端,有兴趣的伙伴可以实践一下如果先开启发布者客户端发布消息,订阅者是否能够收到消息,因此引出小编下面的内容: 即使redis实现了发布订阅(publish/subscribe)的功能,实际工作开发中不推荐使用。 最简单的例子就是上面所说的场景,如果订阅者客户端重启或者断线,那么它重启期间的消息则无法订阅到,导致接受消息失败。
沁溪源
2020/09/03
5180
.net core 使用Redis的发布订阅
Redis是一个性能非常强劲的内存数据库,它一般是作为缓存来使用,但是他不仅仅可以用来作为缓存,比如著名的分布式框架dubbo就可以用Redis来做服务注册中心。接下来介绍一下.net core 使用Redis的发布/订阅功能。 Redis 发布订阅 Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 Redis 客户端可以订阅任意数量的通道。 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 clie
晓晨
2018/06/22
1.4K0
Redis入坟(二)高级特性,发布订阅、事务、Lua脚本
前面我们说通过队列的 rpush 和 lpop 可以实现消息队列(队尾进队头出),但是消费者需要不停地调用 lpop 查看 List 中是否有等待处理的消息(比如写一个 while 循环)。
源码之路
2020/09/04
9470
Redis入坟(二)高级特性,发布订阅、事务、Lua脚本
【云原生进阶之PaaS中间件】第一章Redis-1.7发布订阅模式
        发布订阅模式(Publish-Subscribe Pattern)是一种消息传递模式,其基本原理是消息的发送者(发布者)不会直接发送消息给特定的接收者(订阅者),而是将消息分成不同的类别(频道),然后将消息发送给订阅了这些类别的所有接收者。发布订阅模式在分布式系统中广泛应用,例如实时消息推送、日志收集等。
江中散人_Jun
2023/10/16
4090
【云原生进阶之PaaS中间件】第一章Redis-1.7发布订阅模式
Redis 发布订阅
Redis的发布订阅(Pub/Sub)功能允许客户端订阅一个或多个频道,当某个频道有消息发布时,订阅该频道的客户端会收到相应的消息。发布订阅模式在实际应用中被广泛应用,比如在聊天室、实时数据推送、通知等场景下都可以使用发布订阅模式实现。
玖叁叁
2023/04/15
9180
Redis从入门到放弃(3):发布与订阅
Redis是一个快速、开源的内存数据库,支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。除了基本的数据存储和检索功能外,Redis还提供了许多高级功能,其中之一就是发布订阅(Pub/Sub)。
夕阳也是醉了
2023/10/16
8580
Redis从入门到放弃(3):发布与订阅
redis发布订阅java代码实现
具体源码:github源代码Java实现 发布订阅概念: 订阅,取消订阅和发布实现了发布/订阅消息范式(引自wikipedia),发送者(发布者)不是计划发送消息给特定的接收者(订阅者)。而是发布的消息分到不同的频道,不需要知道什么样的订阅者订阅。订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道什么样的发布者发布的。这种发布者和订阅者的解耦合可以带来更大的扩展性和更加动态的网络拓扑。 发布订阅消息格式 消息是一个有三个元素的多块响应 。 第一个元素是消息类型: subscri
用户5166556
2019/04/16
2.3K0
Redis笔记(五):Redis发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
朝雨忆轻尘
2019/06/19
5720
Redis笔记(五):Redis发布订阅
Redis 发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
子润先生
2021/07/02
5140
Redis 发布和订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
用户9615083
2022/12/25
5010
Redis 发布和订阅
Redis消息发布订阅
谈到「Redis」你可能会想到用作缓存,然而「Redis」除了做缓存还有很多功能。比如做分布式锁,生成全局的「ID」,可以做延迟队列。除了这些「Redis」还可以做消息的发布订阅。
Lvshen
2022/05/05
7150
Redis消息发布订阅
设计模式之发布订阅模式(2) Redis 发布/订阅模式
Redis 通过 PUBLISH 、 SUBSCRIBE 等命令实现了发布订阅模式。该功能提供两种信息机制, 分别是“发布订阅到频道”和“发布订阅到模式”。
用户5546570
2019/06/06
1.6K0
设计模式之发布订阅模式(2) Redis 发布/订阅模式
相关推荐
Redis发布订阅:我想着应该是全网讲解最简单最通俗的文章了吧!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验