环境:SpringBoot + jdk1.8

基础配置参考
https://blog.csdn.net/llll234/article/details/80966952

查看了基础配置那么会遇到一下几个问题:

1.实际应用中可能会订阅多个通道,而一下这种写法不太通用
container.addMessageListener(listenerAdapter(new RedisPmpSub()),new PatternTopic("pmp"));

2.使用过程中使用new RedisPmpSub()配置消息接收对象会有问题。
如果RedisPmpSub既是消息接收类,也是消息处理类。那么如果此时需要注入Bean,会成功吗?

3.考虑后期的扩展性是否能尽量不改变原有代码的基础上,进行扩展

额外的配置文件

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency> <dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>RELEASE</version>
</dependency> <dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

由于GsonUtil依赖的是某个SDK,GsonUtil.toJson(this, BasePubMessage.class)可替换为
new Gson().toJson(this, BasePubMessage.class);
lombok需要下载插件

发布者

枚举定义

考虑到可维护性,采用枚举的方式定义管道RedisChannelEnums

 public enum RedisChannelEnums {

     /**redis频道code定义 需要与发布者一致*/
LIVE_INFO_CHANGE("LIVE_INFO_CHANGE","直播信息改变"), ;
/** 枚举定义+描述 */
private String code;
private String description; RedisChannelEnums(String code, String description) {
this.code = code;
this.description = description;
} /** 根据code获取对应的枚举对象 */
public static RedisChannelEnums getEnum(String code) {
RedisChannelEnums[] values = RedisChannelEnums.values();
if (null != code && values.length > 0) {
for (RedisChannelEnums value : values) {
if (value.code == code) {
return value;
}
}
}
return null;
} /** 该code在枚举列表code属性是否存在 */
public static boolean containsCode(String code) {
RedisChannelEnums anEnum = getEnum(code);
return anEnum != null;
} /** 判断code与枚举中的code是否相同 */
public static boolean equals(String code, RedisChannelEnums calendarSourceEnum) {
return calendarSourceEnum.code == code;
} public String getCode() {
return code;
} public String getDescription() {
return description;
} }

消息模板

为了兼容不同的业务场景,需要定义消息模板对象BasePubMessage
其中ToString方法的作用是将对象转成Json字符

 @Data
public abstract class BasePubMessage { /**发布订阅频道名称*/
protected String channel; protected String extra; @Override
public String toString() {
return GsonUtil.toJson(this, BasePubMessage.class);
} }

消息对象LiveChangeMessage
其中ToString方法的作用是将对象转成Json字符

 @Data
public class LiveChangeMessage extends BasePubMessage { /**直播Ids*/
private String liveIds; @Override
public String toString() {
return GsonUtil.toJson(this, LiveChangeMessage.class);
} }

发布者服务

public interface RedisPub {

    /**
* 集成redis实现消息发布订阅模式-双通道
* @param redisChannelEnums 枚举定义
* @param basePubMessage 消息
*/
void sendMessage(RedisChannelEnums redisChannelEnums, BasePubMessage basePubMessage); }
 @Service
public class RedisPubImpl implements RedisPub { @Resource
private StringRedisTemplate stringRedisTemplate; @Override
public void sendMessage(RedisChannelEnums redisChannelEnums, BasePubMessage basePubMessage) { if(redisChannelEnums ==null || basePubMessage ==null){
return;
} basePubMessage.setChannel(redisChannelEnums.getCode());
stringRedisTemplate.convertAndSend(redisChannelEnums.getCode(), basePubMessage.toString());
System.out.println("发布成功!");
}
}

订阅者

注解配置

RedisConfig作为订阅者的配置类,主要作用是:Redis消息监听器容器、配置消息接收处理类
同时新加入的功能解决了我们上面提出的几个问题

 @Service
@Configuration
@EnableCaching
public class RedisConfig { /**
* 存放策略实例
* classInstanceMap : key-beanName value-对应的策略实现
*/
private ConcurrentHashMap<String, BaseSub> classInstanceMap = new ConcurrentHashMap<>(20); /**
* 注入所有实现了Strategy接口的Bean
*
* @param strategyMap
* 策略集合
*/
@Autowired
public RedisConfig(Map<String, BaseSub> strategyMap) {
this.classInstanceMap.clear();
strategyMap.forEach((k, v) ->
this.classInstanceMap.put(k.toLowerCase(), v)
);
} /**
* Redis消息监听器容器
*
* @param connectionFactory
*
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory); RedisChannelEnums[] redisChannelEnums = RedisChannelEnums.values();
if (redisChannelEnums.length > 0) {
for (RedisChannelEnums redisChannelEnum : redisChannelEnums) {
if (redisChannelEnum == null || StringUtils.isEmpty(redisChannelEnum.getCode()) || redisChannelEnum.getClassName()==null) {
continue;
}
//订阅了一个叫pmp和channel 的通道,多通道
//一个订阅者接收一个频道信息,新增订阅者需要新增RedisChannelEnums定义+BaseSub的子类 String toLowerCase = redisChannelEnum.getClassName().getSimpleName().toLowerCase();
BaseSub baseSub = classInstanceMap.get(toLowerCase);
container.addMessageListener(listenerAdapter(baseSub), new PatternTopic(redisChannelEnum.getCode()));
}
}
return container;
} /**
* 配置消息接收处理类
*
* @param baseSub
* 自定义消息接收类
*
* @return MessageListenerAdapter
*/
@Bean()
@Scope("prototype")
MessageListenerAdapter listenerAdapter(BaseSub baseSub) {
//这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
//也有好几个重载方法,这边默认调用处理器的方法 叫handleMessage 可以自己到源码里面看
//注意2个通道调用的方法都要为receiveMessage
return new MessageListenerAdapter(baseSub, "receiveMessage");
} }

  

@Autowired
public RedisConfig(Map<String, BaseSub> strategyMap) 方法的作用是将所有的配置消息接收处理类注入进来,那么消息接收处理类里面的注解对象也会注入进来。
解决了我们提出的第二个问题 而String toLowerCase = redisChannelEnum.getClassName().getSimpleName().toLowerCase();
BaseSub baseSub = classInstanceMap.get(toLowerCase);
container.addMessageListener(listenerAdapter(baseSub), new PatternTopic(redisChannelEnum.getCode()));
是根据不同的管道对应不同的订阅者,也就是一个订阅者对应一个管道。方便根据不同的业务场景进行处理。
使用这种方式主需要配置redisChannelEnum枚举即可,解决了我们提出的第一个问题。
这样一来,订阅者就变得比较通用了

  

枚举

RedisChannelEnums作用:定义不同管道对应的订阅者,后期增加一个管道类型只需要增加一个枚举即可

 public enum RedisChannelEnums {

     /**redis频道名称定义 需要与发布者一致*/
LIVE_INFO_CHANGE("LIVE_INFO_CHANGE", LiveChangeSub.class, "直播信息改变"), ;
/** 枚举定义+描述 */
private String code;
private Class<? extends BaseSub> className;
private String description; RedisChannelEnums(String code, Class<? extends BaseSub> className, String description) {
this.code = code;
this.className=className;
this.description = description;
} /** 根据code获取对应的枚举对象 */
public static RedisChannelEnums getEnum(String code) {
RedisChannelEnums[] values = RedisChannelEnums.values();
if (null != code && values.length > 0) {
for (RedisChannelEnums value : values) {
if (value.code == code) {
return value;
}
}
}
return null;
} /** 该code在枚举列表code属性是否存在 */
public static boolean containsCode(String code) {
RedisChannelEnums anEnum = getEnum(code);
return anEnum != null;
} /** 判断code与枚举中的code是否相同 */
public static boolean equals(String code, RedisChannelEnums calendarSourceEnum) {
return calendarSourceEnum.code == code;
} public String getCode() {
return code;
} public String getDescription() {
return description;
} public Class<? extends BaseSub> getClassName() {
return className;
}
}

消息模板

BaseSubMessage定义通用的字段,与json字符的通用转换

 @Data
abstract class BaseSubMessage { /** 发布订阅频道名称 */
private String channel; private String extra; private String json; BaseSubMessage(String json) {
if(StringUtils.isEmpty(json)){
return;
} this.json = json;
Map map = new Gson().fromJson(this.json, Map.class);
BeanHelper.populate(this, map);
} }

LiveChangeMessage定义当前业务场景的字段

 @Data
@ToString(callSuper = true)
public class LiveChangeMessage extends BaseSubMessage { /** 直播Ids */
private String liveIds; public LiveChangeMessage(String json) {
super(json);
} }

订阅者服务

BaseSub定义接收消息的通用方法

 public interface BaseSub {

     /**
* 接收消息
* @param jsonMessage json字符
*/
void receiveMessage(String jsonMessage);
}

LiveChangeSub具体消息接收对象

 @Component
public class LiveChangeSub implements BaseSub { /**只是定义的注解测试,可以换成自己的*/
@Autowired
private CategoryMapper categoryMapper; @Override
public void receiveMessage(String jsonMessage) { System.out.println("项目aries-server.....................");
//注意通道调用的方法名要和RedisConfig2的listenerAdapter的MessageListenerAdapter参数2相同
System.out.println("这是 LiveChangeSub" + "-----" + jsonMessage); LiveChangeMessage liveChangeMessage = new LiveChangeMessage(jsonMessage);
System.out.println(liveChangeMessage); Category category = categoryMapper.get(1L);
System.out.println("category:" + category); }
}

总结

发布者配置场景:独立的服务器,独立的项目,A redis缓存服务器
订阅者配置场景:不同于发布者的独立的服务器,独立的项目,A redis缓存服务器
使用场景:一个发布者、一个或者多个订阅者。发布者负责发布消息,订阅者负责接收消息。一旦发布者消息发布出来,那么
订阅者可以通过管道进行监听。同时可以根据不同的管道设置不同的消息接收者或者叫消息处理者。 优点:容易配置,好管理
缺点:由于基于redis去做,不同的redis服务就不适用了。需要考虑消息丢失,持久化的问题。

springboot集成redis实现消息发布订阅模式-双通道(跨多服务器)的更多相关文章

  1. Springboot+Redis(发布订阅模式)跨多服务器实战

    一:redis中发布订阅功能(http://www.redis.cn/commands.html#pubsub) PSUBSCRIBE pattern [pattern -]:订阅一个或者多个符合pa ...

  2. 15天玩转redis —— 第九篇 发布/订阅模式

    本系列已经过半了,这一篇我们来看看redis好玩的发布订阅模式,其实在很多的MQ产品中都存在这样的一个模式,我们常听到的一个例子 就是邮件订阅的场景,什么意思呢,也就是说100个人订阅了你的博客,如果 ...

  3. redis实现消息发布/订阅

    redis实现简单的消息发布/订阅模式. 消息订阅者: package org.common.component; import org.slf4j.Logger; import org.slf4j. ...

  4. redis 的消息发布订阅

    redis支持pub/sub功能(可以用于消息服务器),这个功能类似mq,这里做一个简单的介绍 Pub/Sub Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在R ...

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

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

  6. Spring Data Redis实现消息队列——发布/订阅模式

    一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现. 定义:生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...

  7. redis消息通知(任务队列/优先级队列/发布订阅模式)

    1.任务队列 对于发送邮件或者是复杂计算这样的操作,常常需要比较长的时间,为了不影响web应用的正常使用,避免页面显示被阻塞,常常会将此类任务存入任务队列交由专门的进程去处理. 队列最基础的方法如下: ...

  8. redis实现消息队列&发布/订阅模式使用

    在项目中用到了redis作为缓存,再学习了ActiveMq之后想着用redis实现简单的消息队列,下面做记录.   Redis的列表类型键可以用来实现队列,并且支持阻塞式读取,可以很容易的实现一个高性 ...

  9. Redis消息通知(任务队列和发布订阅模式)

    Redis学习笔记(十)消息通知(任务队列和发布订阅模式) 1. 任务队列 1.1 任务队列的特点 任务队列:顾名思义,就是“传递消息的队列”.与任务队列进行交互的实体有两类,一类是生产者(produ ...

随机推荐

  1. Java第二次作业——数组和String类

    Java第二次作业--数组和String类 学习总结 1.学习使用Eclipse关联jdk源代码,查看String类的equals()方法,截图,并学习其实现方法.举例说明equals方法和==的区别 ...

  2. web应用分页

    1. 场景描述 目前大部分的应用程序中都会用到分页功能,以便减少前端浏览器及后台服务器的压力,以及其他方面的考虑. (1)分页从概念上可分为逻辑分页和物理分页,逻辑分页主要是通过应用程序(前端或者后端 ...

  3. Greenplum客户端访问控制

    1. 问题描述 Greenplum默认是对客户端不开放的,即客户端要访问Greenplum数据库,需要首先开通权限. 2. 解决方案: 2.1.安装greenplum-cc-web控制台. ​ Gp的 ...

  4. Java集合类的概述

    前述 复习一下Java中的集合类,是面试笔试中常考察的一个点,特地做的整理. 什么是集合类? 集合类,也叫容器类.Java集合类可以用来存储数量庞大的对象. 我们和数组进行对比: 数组:存储基本数据类 ...

  5. spring boot 配置mybatis plus 控制台打印sql

    spring boot 版本2.1.5 mybatis plus 版本3.1.1 aplication.properties中添加 logging.level.com.demo.system.mapp ...

  6. [原创]Zabbix3.4_API的python示例

    说明: 1.python版本为:python2.7 2.zabbix版本为:zabbix3.4 3.通过python脚本调用zabbix的api接口可以实现批量增删改查主机的信息. 示例如下: #-* ...

  7. nginx解析漏洞复现

    nginx解析漏洞复现 一.漏洞描述 该漏洞与nginx.php版本无关,属于用户配置不当造成的解析漏洞 二.漏洞原理 1. 由于nginx.conf的如下配置导致nginx把以’.php’结尾的文件 ...

  8. 「PowerBI」Tabular Editor 一个对中文世界很严重的bug即将修复完成

    之前介绍过Tabular Editor这款开源工具,对PowerBI建模来说,非常好用,可以极大的增强自动化水平. 详细可查看此文章: 「PowerBI相关」一款极其优秀的DAX建模工具Tabular ...

  9. Excel催化剂开源第8波-VSTO开发之异步调用方法

    在VSTO开发过程中,因其和普通的Winform开发有点差别,具体细节笔者也说不清楚,大概是VSTO的插件是寄生在Excel中,不属于独立的进程之类的,其异步方法调用时,未能如Winform那样直接用 ...

  10. c语言进阶13-线性表之顺序表

    一. ACM算法:顺序表的查找 顺序表的查找指获取顺序表的第i个元素.对于线性表的顺序存储结构来说,如果我们要实现获取元素的操作(GetElem),即将线性表L中的第i个位置元素值返回.就程序而言,只 ...