SpringBoot Redis 发布订阅模式 Pub/Sub
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的更多相关文章
- redis发布/订阅模式
其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果博主发表了文章,那么100个人就会同时收到通知邮件,除了这个 场 ...
- Redis - 发布/订阅模式
Redis 提供了一组命令可以让开发者实现 “发布/订阅” 模式.“发布/订阅” 可以实现进程间的消息传递,其原理是这样的: “发布/订阅” 模式中包含两种角色,分别是发布者和订阅者.订阅者可以订阅一 ...
- 使用EventBus + Redis发布订阅模式提升业务执行性能
前言 最近一直奔波于面试,面了几家公司的研发.有让我受益颇多的面试经验,也有让我感觉浪费时间的面试经历~因为疫情原因,最近宅在家里也没事,就想着使用Redis配合事件总线去实现下具体的业务. 需求 一 ...
- redis 发布/订阅 模式
发布/订阅模式的命令如下: * 进入发布订阅模式的客户端,不能执行除发布订阅模式以上命令的其他命令,否则出错.
- Redis教程03——Redis 发布/订阅(Pub/Sub)
Pub/Sub 订阅,取消订阅和发布实现了发布/订阅消息范式(引自wikipedia),发送者(发布者)不是计划发送消息给特定的接收者(订阅者).而是发布的消息分到不同的频道,不需要知道什么样的订阅者 ...
- 使用EventBus + Redis发布订阅模式提升业务执行性能(下)
前言 上一篇博客上已经实现了使用EventBus对具体事件行为的分发处理,某种程度上也算是基于事件驱动思想编程了.但是如上篇博客结尾处一样,我们源码的执行效率依然达不到心里预期.在下单流程里我们明显可 ...
- redis 发布订阅(pub/sub )
- Redis进阶篇:发布订阅模式原理与运用
"65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?" "那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆 ...
- 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...
随机推荐
- [uoj173]鏖战表达式
2018年论文题,上接loj2506,主要是论文中的第4章,也可快速跳至原题解 5.平衡树的嵌套问题 平衡树嵌套 所谓平衡树嵌套,就是若干棵平衡树,其中若干棵平衡树的根会指向另一颗平衡树上的一个节点 ...
- [loj3046]语言
定义$S_{i}$表示第$i$条链所包含的点的集合,$(x,y)$合法当且仅当$x\ne y$且$\exists i,\{x,y\}\subseteq S_{i}$(答案即$\frac{合法点对数}{ ...
- [nowcoder5669H]Harder Gcd Problem
题目相当于问1-n中最多能选出多少对不互素无交集的二元组,并要求方案 构造:将所有数放入其最小质因子对应的集合,若素数p所对应的集合元素个数为奇数且$p\ne 2$且$2p\le n$,那么就将$2p ...
- 【Design Patterns】(1)概述
设计模式 -- 概述 2019-07-17 22:43:32 by冲冲 1. 简介 ① 设计模式 是软件开发人员在软件开发过程中,针对一般问题的最佳解决方案,该方案能够被程序员反复应用于解决类似问 ...
- Codeforces 1361C - Johnny and Megan's Necklace(欧拉回路)
Codeforces 题目传送门 & 洛谷题目传送门 u1s1 感觉这个题作为 D1C 还是蛮合适的-- 首先不难发现答案不超过 \(20\),所以可以直接暴力枚举答案并 check 答案是否 ...
- HDU 6036 Division Game
HDU 6036 Division Game 考虑每堆石头最多操作 $ \sum e $ 次,考虑设 $ f(x) $ 表示某一堆石头(最开始都是一样的)操作 $ x $ 次后变成了 $ 1 $ 的方 ...
- HDU 5322 Hope
HDU 5322 Hope 考虑 $ dp[n] $ 表示 长度为 $ n $ 的所有排列的答案. 首先,对于一个排列来说,如果最大值在 $ i $ 位置,那么前 $ i - 1 $ 个数必然与 $ ...
- 【机器学习与R语言】1-机器学习简介
目录 1.基本概念 2.选择机器学习算法 3.使用R进行机器学习 1.基本概念 机器学习:发明算法将数据转化为智能行为 数据挖掘 VS 机器学习:前者侧重寻找有价值的信息,后者侧重执行已知的任务.后者 ...
- js ajax 请求
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 33. Implement strStr()
http://blog.csdn.net/justdoithai/article/details/51287649 理解与分析 Implement strStr() My Submissions Qu ...