一: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. 09. jenkins配置不同用户显示不同视图

    jenkins配置不同用户显示不同视图 一.新建用户 1.1 新建用户 Manage Jenkins -> Manage Users -> 新建用户     1.2 我创建了三个用户,分别 ...

  2. 20190919-02安装Xshell和CRT远程工具 000 008

    Linux远程登录及相关工具介绍 Linux一般作为服务器使用,而服务器一般放在机房,你不可能在机房操作你的Linux服务器.这时我们就需要远程登录到Linux服务器来管理维护系统. Linux系统中 ...

  3. PHP的九个超全局变量

    1. 什么是超全局变量 PHP官网:超全局变量 超全局变量就是在全部作用域中始终可用的内置变量. 全局作用域.函数作用域都可以使用的PHP内置变量. 在函数或方法中无需执行 global $varia ...

  4. 不用写代码也能做表单 —— 加载meta即可

    做增删改查要写多少代码? 一个表单一套代码,十个表单十套代码吗? 我这么懒,怎么会写这么多代码? 我想做到:即使一百个表单也只需要一套代码(而且不需要复制粘贴).实现多个表单,只需要加载不同的meta ...

  5. Centos7,PHP7安装swoole

    Swoole详细介绍及如何使用,这里暂时不做说明,可以参考一下文档:https://wiki.swoole.com/ 源码编译安装 下载地址如下: https://github.com/swoole/ ...

  6. turtle角度坐标体系

    seth()改变海龟的行进角度 例如 让海龟的方向朝着45°方向行进 turtle.seth(45) 让海龟的方向朝着-135°反方向行进 turtle.seth(-135) turtle.left( ...

  7. Spark Driver Program剖析

    SparkContext是通往Spark集群的唯一入口,是整个Application运行调度的核心. 一.Spark Driver Program Spark Driver Program(以下简称D ...

  8. 手把手教你配置git和git仓库

    今天是git专题的第二篇,我们来介绍一下git的基本配置,以及建立一个git仓库的基本方法. 首先申明一点,本文不会介绍git的安装.一方面是大部分个人PC的系统当中都是已经装好了git的,另外一方面 ...

  9. 8.Kafka offset机制

  10. Flutter学习二之Dart语言介绍

    上次我记录了Flutter的环境搭建,这次来简单记录一下Drat语言,Flutter是 Google推出并开源的移动应用开发框架,开发语言是Dart,那么Dart语言和其他的语言在语法上有上面区别呢, ...