一: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(发布订阅模式)跨多服务器实战的更多相关文章

  1. SpringBoot Redis 发布订阅模式 Pub/Sub

    SpringBoot Redis 发布订阅模式 Pub/Sub 注意:redis的发布订阅模式不可以将消息进行持久化,订阅者发生网络断开.宕机等可能导致错过消息. Redis命令行下使用发布订阅 pu ...

  2. redis发布/订阅模式

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

  3. Redis - 发布/订阅模式

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

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

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

  5. redis 发布/订阅 模式

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

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

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

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

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

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

    今天小黑哥来跟大家介绍一下 Redis 发布/订阅功能. 也许有的小伙伴对这个功能比较陌生,不太清楚这个功能是干什么的,没关系小黑哥先来举个例子. 假设我们有这么一个业务场景,在网站下单支付以后,需要 ...

  9. springboot集成redis实现消息发布订阅模式-双通道(跨多服务器)

    基础配置参考https://blog.csdn.net/llll234/article/details/80966952 查看了基础配置那么会遇到一下几个问题: 1.实际应用中可能会订阅多个通道,而一 ...

随机推荐

  1. Q20200511-01 翻转字符串

    需求:做一函数将字符串倒转过来 程序: package test4; public class Reverse { public static String reverse(String origin ...

  2. 20190923-07Linux搜索查找类 000 015

    find 查找文件或者目录 find指令将从指定目录向下递归地遍历其各个子目录,将满足条件的文件显示在终端. 1.基本语法 find [搜索范围] [选项] 2.选项说明 表1-27 选项 功能 -n ...

  3. vps+v_2_ray+proxychains

    电脑系统换到Linux快半年了,之前一直没有解决的问题是怎么上google,毕竟有些东西还是google上好找一点.最近不想复习,没想到自己成功搭了个梯子,着实把惊喜了我一把.下面记录一下过程. 首先 ...

  4. GaussDB(DWS)应用实战:对被视图引用的表进行DDL操作

    摘要:GaussDB(DWS)是从Postgres演进过来的,像Postgres一样,如果表被视图引用的话,特定场景下,部分DDL操作是不能直接执行的. 背景说明 GaussDB(DWS)是从Post ...

  5. jenkins安装和邮件配置

    一.jenkins下载 Jenkins的下载地址是https://jenkins.io/download/,下载的时候可以选择各个版本的以及对应操作系统的版本,一般你下载的时候下载通用的.war文件即 ...

  6. python判断链表是否有环

    思路:使用快慢指针,快指针每次走两步,慢指针每次走一步,如果有环,则一定会快慢指针指向同一结点: 假设环的长度为n,先让一个指针走n步,另一个再开始走,当他们指针指向同一结点时,该结点就是环入口点 ( ...

  7. 移动端 取消0.3ms的延迟 两种方案解决

    在index.html中添加一下代码 <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fas ...

  8. hystrix熔断器之配置

    HystrixCommandProperties命令执行相关配置: hystrix.command.[commandkey].execution.isolation.strategy 隔离策略THRE ...

  9. Vue 3.0 来了,我们该做些什么?

    靓仔路过,不要错过 想必 Vue3.0 发布这件事,大家都知道了. 我也是从朋友圈的转发得知此事,博客平台.公众号.朋友圈基本都有这么一条新闻,可见 Vue3.0 的被期待程度,因为 React 16 ...

  10. Hive使用Calcite CBO优化流程及SQL优化实战

    目录 Hive SQL执行流程 Hive debug简单介绍 Hive SQL执行流程 Hive 使用Calcite优化 Hive Calcite优化流程 Hive Calcite使用细则 Hive向 ...