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. [hdu6989]Didn't I Say to Make My Abilities Average in the Next Life?!

    显然问题即求$\frac{\sum_{x\le l\le r\le y}(\max_{l\le i\le r}a_{i}+\min_{l\le i\le r}a_{i})}{(y-x+2)(y-x+1 ...

  2. Class类简介

    Class类 Java中所有的类.接口.枚举.注解.数组.基本数据类型.void关键字,都有Class对象.通过Class对象可以得到类的完整结构,一个Class对象在jvm中只有一个实例. 获取类实 ...

  3. 干掉idea视图黄色警告

    最近在写jsp代码黄色很烦人,安装codeglance插件小地图感觉也是很不舒服 ,百度了一下可以取消警告: https://blog.csdn.net/qq_40634961/article/det ...

  4. Codeforces 724G - Xor-matic Number of the Graph(线性基)

    Codeforces 题目传送门 & 洛谷题目传送门 一道还算不套路的线性基罢-- 首先由于图不连通,并且不同连通块之间的点显然不可能产生贡献,因此考虑对每个连通块单独计算贡献.按照 P415 ...

  5. 洛谷 P3246 - [HNOI2016]序列(单调栈+前缀和)

    题面传送门 这道题为什么我就没想出来呢/kk 对于每组询问 \([l,r]\),我们首先求出区间 \([l,r]\) 中最小值的位置 \(x\),这个可以用 ST 表实现 \(\mathcal O(n ...

  6. seq生成格式化字符

    [root@ansz.quan.bbs ~]$seq -s "+" 10 1+2+3+4+5+6+7+8+9+10 seq为生成数字序列 -s 分隔符

  7. Python三元表达式,列表推导式,字典生成式

    目录 1. 三元表达式 2. 列表推导式 3. 字典生成式 3.1 字典生成式 3.2 zip()方法 1. 三元表达式 """ 条件成立时的返回值 if 条件 else ...

  8. mysql—Linux系统直接进入mysql服务器,并实现一些基础操作

    首先,我们需要通过以下命令来检查MySQL服务器是否启动: ps -ef | grep mysqld 如果MySql已经启动,以上命令将输出mysql进程列表 如果mysql未启动,你可以使用以下命令 ...

  9. Spring Boot 热启动插件

    1. maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId ...

  10. day12 form组件

    day12 form组件 今日内容 form组件前戏 form组件基本定义 form组件数据校验功能 form组件渲染标签 form组件提示信息 数据校验进阶 form组件补充 form组件源码探索 ...