Springboot+Redis(发布订阅模式)跨多服务器实战
一:redis中发布订阅功能(http://www.redis.cn/commands.html#pubsub)
PSUBSCRIBE pattern [pattern …]:订阅一个或者多个符合pattern格式的频道
PUBLISH channel message:发布消息到chanel中
PUBSUB subcommand [argument [argument …]]:查看订阅与发布系统状态
PUNSUBSCRIBE [pattern [pattern …]]:退订所有符合格式的频道
SUBSCRIBE channel [channel …]:订阅一个或者多个频道
UNSUBSCRIBE [channel [channel …]]:取消订阅频道
二:实战使用reids中的发布订阅模式解决部署在阿里服务与本地后台服务的接口调用不通问题(跨多服务器)
当然也可使用更为强大的消息中间件(RabbitMQ、ActiveMQ、RocketMQ、Kafka、ZeroMQ)
1:redis使用到的maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- springboot1.5版本使用jedis,2.0以上版本使用lettuce,本项目使用jedis,所以需要排除lettuce -->
<exclusions>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
<!--
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
-->
</exclusions>
</dependency>
2:application.yml配置
redis:
host: localhost
port: 8379
password: 123456
database: 2
timeout: 50s
# 如果使用的jedis 则将lettuce改成jedis即可
jedis:
pool:
max-active: 8
max-idle: 8
min-idle: 0
3:publisher发布者发布消息
/**
* 建立发布者,通过频道发布消息
* @param key 发布者
* @param value 消息
*/
public void publish(String key,Object value){
this.redisTemplate.convertAndSend(key,value);
}
redisUtils.publish(RedisTopicEnums.TOPIC_DISCOVER.getTopic(),message);
4:第一种实现方法
/**
* redis消息监听器容器
* @param connectionFactory
* @param healthyListenerAdapter 健康扫描消息订阅处理器
* @param settingsListenerAdapter 配置健康扫描消息订阅处理器
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter healthyListenerAdapter,
MessageListenerAdapter settingsListenerAdapter
) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//设备健康扫描绑定消息订阅处理器
container.addMessageListener(healthyListenerAdapter, new PatternTopic("healthy_topic"));
//设备配置扫描并绑定消息订阅处理器
container.addMessageListener(settingsListenerAdapter, new PatternTopic("settings_topic"));
return container;
} /**
* 设备健康消息订阅处理器,并指定处理方法(利用反射的机制调用消息处理器的业务方法)
* @param receiver
* @return
*/
@Bean
MessageListenerAdapter healthyListenerAdapter(ReceiverRedisMessage receiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(receiver, "healthy");
return messageListenerAdapter;
} /**
* 设备健康消息订阅处理器,并指定处理方法
* @param receiver
* @return
*/
@Bean
MessageListenerAdapter settingsListenerAdapter(ReceiverRedisMessage receiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(receiver, "settings");
return messageListenerAdapter;
}
缺点:1:考虑实际运用中可能会订阅多个主题,每加一个主题(Topic)都需要使用container.addMessageListener(listenerAdapter,new PatternTopic("topic"));不合理
2:考虑到后期尽量不该变原有代码进行扩展,推荐使用下面第二种方式实现(保证开闭原则)
5:第二种实现方法:定义订阅者接收消息器接口
/**
* 订阅者接收消息的基类
* @author : ywb
* @createdDate : 2020/8/6
* @updatedDate
*/
public interface Subscriber extends MessageListener { /**
* 类型
* @return
*/
default String getType() {
return this.getClass().getSimpleName();
} /**
* 通道名称
* @return
*/
String getTopic(); }
6:定义不同主题枚举类型,后期增加一个管道,增加一个枚举信息即可
/**
* 定义不同主题类型
* @author : ywb
* @createdDate : 2020/8/7
* @updatedDate
*/
public enum RedisTopicEnums { /**
* redis主题名称定义 需要与发布者一致
*
*/
TOPIC_DISCOVERY("topic:discovery", "设备发现变更Topic"), TOPIC_HEALTHY("topic:healthy", "健康扫描的设备Topic"), TOPIC_SETTINGS("topic:settings", "配置扫描变更的设备Topic"), TOPIC_DISCOVER("topic:discover", "发现设备Topic"), ;
/**
* 主题名称
*/
private String topic; /**
* 描述
*/
private String description; RedisTopicEnums(String topic, String description) {
this.topic = topic;
this.description = description;
} public String getTopic() {
return topic;
} public String getDescription() {
return description;
} }
7:实现多个订阅者,后续增加一个订阅者,只需要多加上一个订阅者类,从而不用改动redis消息 监听容器配置
7.1:设备健康扫描订阅者
/**
* 设备健康扫描的订阅者
*
* @author : ywb
* @createdDate : 2020/8/7
* @updatedDate
*/
@Component
@Slf4j
public class HealthySubscriber implements Subscriber { @Autowired
private DeviceService deviceService; @Autowired
private RedisTemplate<String, Object> redisTemplate; @Override
public String getTopic() {
return RedisTopicEnums.TOPIC_HEALTHY.getTopic();
} @Override
public void onMessage(Message message, byte[] pattern) { String deviceIds = (String) redisTemplate.getValueSerializer().deserialize(message.getBody()); log.info(">> 订阅消息,设备健康异常编号:{}", deviceIds); // TODO 这里是收到通道的消息之后执行的方法
String[] split = deviceIds.split(","); Map<String, Set<Integer>> idsMap = TokenSplit.getDeviceIdRegex(split); for (Map.Entry<String, Set<Integer>> stringSetEntry : idsMap.entrySet()) {
DeviceHandle healthyHandle = new DeviceHealthyHandle();
healthyHandle.respondHandle(stringSetEntry.getValue());
} }
}
7.2:配置扫描订阅者
/**
* 设备配置变更订阅者
*
* @author : ywb
* @createdDate : 2020/8/7
* @updatedDate
*/
@Component
@Slf4j
public class SettingsSubscriber implements Subscriber { @Autowired
private RedisTemplate<String, Object> redisTemplate; @Override
public String getTopic() {
return RedisTopicEnums.TOPIC_SETTINGS.getTopic();
} @Override
public void onMessage(Message message, byte[] pattern) { //使用redis convertAndSend发布消息,订阅者获取字符串字节必须要反序列
String deviceIds = (String) redisTemplate.getValueSerializer().deserialize(message.getBody()); log.info(">>订阅消息,设备配置变更编号:{}", deviceIds); // TODO 这里是收到通道的消息之后执行的方法
String[] split = deviceIds.split(","); Map<String, Set<Integer>> idsMap = TokenSplit.getDeviceIdRegex(split); for (Map.Entry<String, Set<Integer>> stringSetEntry : idsMap.entrySet()) {
DeviceScannerHandle scannerHandle = new DeviceScannerHandle();
scannerHandle.respondHandle(stringSetEntry.getValue());
}
}
}
8:redisConfig配置,消息监听器容器配置
@Configuration
public class RedisConfig { /**
* 自定义 redisTemplate<String, Object>
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory); ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// om.activateDefaultTyping(BasicPolymorphicTypeValidator.builder().build(), ObjectMapper.DefaultTyping.EVERYTHING);
om.activateDefaultTyping(new LaissezFaireSubTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
om.setTimeZone(TimeZone.getTimeZone("GMT+8"));
// 不转换值为 null 的对象
// om.setSerializationInclusion(JsonInclude.Include.NON_NULL); Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(om); // key 采用 string 的序列化方式
template.setKeySerializer(new StringRedisSerializer());
// value 采用 jackson 的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash 的 key 采用 string 的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// hash 的 value 采用 jackson 的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet(); return template;
} /**
* DependencyDescriptor
* 重点
* 首先判断注入的类型,如果是数组、Collection、Map,则注入的是元素数据,即查找与元素类型相同的Bean,注入到集合中。
* 强调下Map类型,Map的 key 为Bean的 name,value 为 与定义的元素类型相同的Bean。
*将所有相同类型(实现了同一个接口)的Bean,一次性注入到集合类型中,具体实现查看spring源码
*
* 获取Subscriptor接口所有的实现类
* 注入所有实现了接口的Bean
* 将所有的配置消息接收处理类注入进来,那么消息接收处理类里面的注解对象也会注入进来
*/
@Autowired
private transient List<Subscriber> subscriptorList; @Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { //创建一个消息监听对象
RedisMessageListenerContainer container = new RedisMessageListenerContainer(); //将监听对象放入到容器中
container.setConnectionFactory(connectionFactory); if (this.subscriptorList != null && this.subscriptorList.size() > 0) {
for (Subscriber subscriber : this.subscriptorList) { if (subscriber == null || StringUtils.isBlank(subscriber.getTopic())) {
continue;
}
//一个订阅者对应一个主题通道信息
container.addMessageListener(subscriber, new PatternTopic(subscriber.getTopic()));
}
} return container;
}
注:编写代码要善于运用设计模式
Springboot+Redis(发布订阅模式)跨多服务器实战的更多相关文章
- SpringBoot Redis 发布订阅模式 Pub/Sub
SpringBoot Redis 发布订阅模式 Pub/Sub 注意:redis的发布订阅模式不可以将消息进行持久化,订阅者发生网络断开.宕机等可能导致错过消息. Redis命令行下使用发布订阅 pu ...
- redis发布/订阅模式
其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果博主发表了文章,那么100个人就会同时收到通知邮件,除了这个 场 ...
- Redis - 发布/订阅模式
Redis 提供了一组命令可以让开发者实现 “发布/订阅” 模式.“发布/订阅” 可以实现进程间的消息传递,其原理是这样的: “发布/订阅” 模式中包含两种角色,分别是发布者和订阅者.订阅者可以订阅一 ...
- 使用EventBus + Redis发布订阅模式提升业务执行性能
前言 最近一直奔波于面试,面了几家公司的研发.有让我受益颇多的面试经验,也有让我感觉浪费时间的面试经历~因为疫情原因,最近宅在家里也没事,就想着使用Redis配合事件总线去实现下具体的业务. 需求 一 ...
- redis 发布/订阅 模式
发布/订阅模式的命令如下: * 进入发布订阅模式的客户端,不能执行除发布订阅模式以上命令的其他命令,否则出错.
- 使用EventBus + Redis发布订阅模式提升业务执行性能(下)
前言 上一篇博客上已经实现了使用EventBus对具体事件行为的分发处理,某种程度上也算是基于事件驱动思想编程了.但是如上篇博客结尾处一样,我们源码的执行效率依然达不到心里预期.在下单流程里我们明显可 ...
- 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...
- Redis 发布订阅,小功能大用处,真没那么废材!
今天小黑哥来跟大家介绍一下 Redis 发布/订阅功能. 也许有的小伙伴对这个功能比较陌生,不太清楚这个功能是干什么的,没关系小黑哥先来举个例子. 假设我们有这么一个业务场景,在网站下单支付以后,需要 ...
- springboot集成redis实现消息发布订阅模式-双通道(跨多服务器)
基础配置参考https://blog.csdn.net/llll234/article/details/80966952 查看了基础配置那么会遇到一下几个问题: 1.实际应用中可能会订阅多个通道,而一 ...
随机推荐
- python基础:异常捕捉
一.异常 python在程序运行过程中,可能会出现一些错误和异常,导致程序停止运行.我们可以通过捕捉异常,并对异常进行处理,使得程序可以正常运行 异常有很多类型,可以根据类型挨个捕捉.也可统一捕获: ...
- python模块hashlib、xlwt、pymysql
一.xlwt xlwt是python第三方模块,主要是对excel的写操作.xlwt使用时必须先安装. 1.安装 在操作系统的cmd窗口输入pip install xlwt回车即可在线安装. 安装完成 ...
- 一文解开java中字符串编码的小秘密
目录 简介 Unicode的发展史 Unicode详解 UTF-8 UTF-16 UTF-32 Null-terminated string 和变种UTF-8 简介 在本文中你将了解到Unicode和 ...
- VMware安装Centos7并联网使用
一.安装VMware VMwareworkstation官方下载地址: https://www.vmware.com/cn/products/workstation-pro/workstation-p ...
- vmware启动winodws时报错弹出【无法连接MKS:套接字连接尝试次数太多;正在放弃;】
启动虚拟机时报错 解决办法:(其实就是有关于虚拟机的服务没有起) win+R输入services.msc,将所有有关vmware的服务都起起来即可
- 蒲公英 · JELLY技术周刊 Vol.22: npm i react-router@6.0.0-beta.0
蒲公英 · JELLY技术周刊 Vol.22 近期 React Router 已经释出了 6.x 的 beta 版本,正式版本已经不远了,作为 React 生态中的重要组成部分,React Route ...
- GitHub常用上传文件的两种方法 附带常见的问题及Git安装教程
从早上下课到现在一直在琢磨如何给Github下载本地文件,中午饭都没吃.还好是解决了,感觉挺有成就感的.O(∩_∩)O哈哈~ 好哒 闲话不说,说重点. 一.git的安装 百度云:http://pan. ...
- JVM七大垃圾回收器上篇Serial、ParNeW、Parallel Scavenge、 Serial Old、 Parallel Old、 CMS、 G1
GC逻辑分类 垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商.不同版本的JVM来实现. 由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本. 从不同角度分析垃圾收 ...
- php处理的图片无法进CDN缓存
今天发现线上有个问题,线上一个图片域名,在前端已经加了CDN缓存,不落缓存,则用PHP动态实现图片缩放,但经PHP处理过的图片输出后,每次都要从后端读取,后端服务器压力瞬间增加,经分析,PHP中没有作 ...
- 2020年秋季最新Python详细入门教程!全网最新最全
1. import # -*- coding: utf-8 -*- ## 引入新的包 import turtle import pickle # 文件操作 import tensorflow as t ...