SpringBoot Redis 发布订阅模式 Pub/Sub

注意:redis的发布订阅模式不可以将消息进行持久化,订阅者发生网络断开、宕机等可能导致错过消息。

Redis命令行下使用发布订阅

publish 发布

发布者通过以下命令可以往指定channel发布message

redis> publish channel message

subscribe 订阅

订阅者通过以下命令可以订阅一个或多个频道,如果频道不存在则会创建

redis> subscribe channel [channel ...]

对于redis的发布订阅的命令就这么简单。那么接下来我们在springboot中如何使用发布订阅的功能呢?

SpringBoot中使用Redis的发布订阅功能

添加依赖配置redis信息和连接池什么的就不说了,如果添加的有commons-pool2依赖的话,会自动帮我们配置redis连接池的

发布者

相对于订阅者来说,发布者的实现方式很简单,以下方式就可以往channel中发送message了。


@Resource
private RedisTemplate<String, Object> redisTemplate; public void publish(){
// 使用高级的redisTemplate
redisTemplate.convertAndSend("channel","message"); // 使用低级的connection 实际上redisTemplate的底层就是使用的下面的方式
redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
connection.publish("channel".getBytes(StandardCharsets.UTF_8), "message".getBytes(StandardCharsets.UTF_8));
return null;
}
}, true);
// true这个参数意思是 是否将redis连接暴露给回调代码,大多数情况下设置true就可以了,往后深入的话可以看到
RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse)); 如果为false的话会创建redis连接的代理
}

订阅者

订阅者因为涉及到连接、线程等 所以内容相对会多一点

    @Resource
private RedisTemplate<String, Object> redisTemplate; public void subscribe() {
redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
// 我定义了一个全局的 ConcurrentHashMap 用来存放连接 因为后面的取消订阅的线程要和订阅的线程用同一个连接
map.put("connection",connection); // subscribe 按频道订阅 该方法会阻塞该线程 只有取消订阅才会释放该线程
connection.subscribe(new MessageListener() {
@Override
public void onMessage(Message message, byte[] pattern) {
log.info("接收到消息");
System.out.println(new String(message.getBody()));
}
}, "channelOne".getBytes(StandardCharsets.UTF_8), "channelTwo".getBytes(StandardCharsets.UTF_8)); // 按模式订阅 pSubscribe 只有取消订阅才会释放该线程
// connection.pSubscribe(new MessageListener() {
// @Override
// public void onMessage(Message message, byte[] pattern) {
// System.out.println(new String(message.getBody()));
// }
// }, "patternOne".getBytes(StandardCharsets.UTF_8), "patternOne".getBytes(StandardCharsets.UTF_8));
return null;
}
}, true);
}

如何取消订阅呢?从刚才的map里取到连接

    RedisConnection the = map.get("connection");
Subscription subscription = the.getSubscription();
subscription.unsubscribe();

消息监听容器

上面的那种订阅为低级订阅,由于连接在调用subscribe的时候会导致当前线程阻塞,这种方式需要对每个监听器连接和线程管理,所以spring提供了RedisMessageListenerContainer类来帮我们完成这些工作。

RedisMessageListenerContainer顾名思义可以知道它是一个消息监听容器

详情请参考官方文档

如何实现

@Configuration
public class DefaultMessageListenerContainerConfig { @Bean
public RedisMessageListenerContainer container(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
// 官方推荐我们使用自定义的线程池或者使用TaskExecutor
container.setTaskExecutor(executor());
container.addMessageListener(new MessageListener() {
@Override
public void onMessage(Message message, byte[] pattern) {
System.out.println(Thread.currentThread().getName() + ": " + new String(message.getBody()));
}
}, new ChannelTopic("message"));
return container;
} @Bean
public TaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
}

这个时候我们在redis命令行内使用 publish channel message 的时候,我们的spring程序就可以订阅到消息了。

再说下 MessageListenerAdapter

我们可以通过 MessageListenerAdapter 消息接收者包装进去,消息接收者不会和redis有任何耦合。

官方文档给了spring传统的xml的方式配置的,下面我给出基于configuration配置的代码

public interface MessageDelegate {
void handleMessage(String message);
} public class DefaultMessageDelegate implements MessageDelegate {
@Override
public void handleMessage(String message) {
System.out.println(message);
}
} @Configuration
public class MessageListenerContainerConfig { @Autowired
private DefaultMessageDelegate defaultMessageDelegate; @Bean
public RedisMessageListenerContainer container(RedisConnectionFactory factory,
MessageListenerAdapter messageListenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.setTaskExecutor(executor()); Map<MessageListenerAdapter, Collection<? extends Topic>> map = new HashMap<>();
List<ChannelTopic> channelTopics = new ArrayList<>();
ChannelTopic channelTopic = new ChannelTopic("message");
channelTopics.add(channelTopic);
map.put(messageListenerAdapter, channelTopics);
container.setMessageListeners(map); return container;
} @Bean
public TaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
} @Bean
public MessageListenerAdapter messageListenerAdapter() {
// handleMessage 参数消息来的时候要调用的方法 默认是 handleMessage
return new MessageListenerAdapter(defaultMessageDelegate, "handleMessage");
}
}

如果我们要在程序运行时添加订阅或者取消订阅的时候该怎么办呢?

我们需要提前准备好消息侦听器,添加的时候把侦听器注入到消息容器

取消的时候就调用消息容器的remove方法把侦听器删除掉即可。

SpringBoot Redis 发布订阅模式 Pub/Sub的更多相关文章

  1. redis发布/订阅模式

    其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果博主发表了文章,那么100个人就会同时收到通知邮件,除了这个 场 ...

  2. Redis - 发布/订阅模式

    Redis 提供了一组命令可以让开发者实现 “发布/订阅” 模式.“发布/订阅” 可以实现进程间的消息传递,其原理是这样的: “发布/订阅” 模式中包含两种角色,分别是发布者和订阅者.订阅者可以订阅一 ...

  3. 使用EventBus + Redis发布订阅模式提升业务执行性能

    前言 最近一直奔波于面试,面了几家公司的研发.有让我受益颇多的面试经验,也有让我感觉浪费时间的面试经历~因为疫情原因,最近宅在家里也没事,就想着使用Redis配合事件总线去实现下具体的业务. 需求 一 ...

  4. redis 发布/订阅 模式

    发布/订阅模式的命令如下: * 进入发布订阅模式的客户端,不能执行除发布订阅模式以上命令的其他命令,否则出错.

  5. Redis教程03——Redis 发布/订阅(Pub/Sub)

    Pub/Sub 订阅,取消订阅和发布实现了发布/订阅消息范式(引自wikipedia),发送者(发布者)不是计划发送消息给特定的接收者(订阅者).而是发布的消息分到不同的频道,不需要知道什么样的订阅者 ...

  6. 使用EventBus + Redis发布订阅模式提升业务执行性能(下)

    前言 上一篇博客上已经实现了使用EventBus对具体事件行为的分发处理,某种程度上也算是基于事件驱动思想编程了.但是如上篇博客结尾处一样,我们源码的执行效率依然达不到心里预期.在下单流程里我们明显可 ...

  7. redis 发布订阅(pub/sub )

  8. Redis进阶篇:发布订阅模式原理与运用

    "65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?" "那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆 ...

  9. 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...

随机推荐

  1. [atARC121E]Directed Tree

    令$b_{a_{i}}=i$,那么问题即要求$i$不是$b_{i}$的祖先,也即$b_{i}$不严格在$i$的子树中 显然$a_{i}$和$b_{i}$一一对应,因此我们不妨统计$b_{i}$的个数 ...

  2. Python技法1:变长和定长序列拆分

    Python中的任何序列(可迭代的对象)都可以通过赋值操作进行拆分,包括但不限于元组.列表.字符串.文件.迭代器.生成器等. 元组拆分 元组拆分是最为常见的一种拆分,示例如下: p = (4, 5) ...

  3. JSOI 2008 最小生成树计数

    JSOI 2008 最小生成树计数 今天的题目终于良心一点辣 一个套路+模版题. 考虑昨天讲的那几个结论,我们有当我们只保留最小生成树中权值不超过 $ k $ 的边的时候形成的联通块是一定的. 我们可 ...

  4. Atcoder Regular Contest 096 C - Everything on It(组合数学)

    Atcoder 题面传送门 & 洛谷题面传送门 简单题,由于这场 arc 的 F 是 jxd 作业而我不会做,所以只好来把这场的 E 水掉了. 我们记 \(f(i)\) 为钦定 \(i\) 个 ...

  5. 2015百度之星之-IP聚合

    IP聚合  Accepts: 138  Submissions: 293  Time Limit: 2000/1000 MS (Java/Others)  Memory Limit: 6553 ...

  6. 爬虫动态渲染页面爬取之selenium驱动chrome浏览器的使用

    Selenium是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样,可以用其进行网页动态渲染页面的爬取. 支持的浏览器包括IE(7, 8, 9, 10 ...

  7. mysql-centos8下安装

    参考文章 1.下载安装包 客服端与服务端 依赖包 2.linux下检查是否安装 rpm -qa | grep -i mysql 安装过会显示软件名称,没安装过就是空的 3.安装包传到虚拟机 先需要把安 ...

  8. 日常Java 2021/11/17

    应用程序转换成Applet 将图形化的Java应用程序(是指,使用AWT的应用程序和使用java程序启动器启动的程序)转换成嵌入在web页面里的applet是很简单的.下面是将应用程序转换成.Appl ...

  9. 大厂高频面试题Spring Bean生命周期最详解

    Spring作为当前Java最流行.最强大的轻量级框架.Spring Bean的生命周期也是面试高频题,了解Spring Bean周期也能更好地帮助我们解决日常开发中的问题.程序员应该都知道Sprin ...

  10. three.js很好玩

    能用鼠标拉着转. https://files.cnblogs.com/files/blogs/714801/%E7%A9%BA%E9%97%B4%E5%87%A0%E4%BD%95.7z var po ...