1 背景
一个项目有三个模块,网关,用户管理,业务主体;客户的环境安装中间件很麻烦,而且项目很小(没必要部署),实现的需要是网关接口拦截token失效插入一个审计日志到业务主体项目中,由于考虑到网关的纯洁性,所以使用feign调用的方式直接pass掉了,使用消息中间件也是一个好办法(但是项目安装比较麻烦因为你是政府项目),所以就想到了用redis的发布订阅的模式。
2.1订阅配置
频道常量
public class MessageConstant {
/**
* 订阅的频道
*/
public static final String AUDIT_LOG_CHANNEL = "AUDIT_LOG_CHANNEL";
}
redis配置类
import net.cc.support.MessageReceiverSupport;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
/**
* @author yueyang
* @since 2021/02/05 14:47
**/
@Configuration
@AutoConfigureAfter({MessageReceiverSupport.class})
public class RedisPubSubConfig {
/**
* Redis消息监听器容器
* 这个容器加载了RedisConnectionFactory和消息监听器
* 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
* @param connectionFactory 链接工厂
* @param adapter 适配器
* @return redis消息监听容器
*/
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter adapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//可以添加多个 messageListener
container.addMessageListener(adapter, new PatternTopic(MessageConstant.AUDIT_LOG_CHANNEL));
return container;
}
/**
* 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
* 将MessageReceiver注册为一个消息监听器,可以自定义消息接收的方法(handleMessage)
* 如果不指定消息接收的方法,消息监听器会默认的寻找MessageReceiver中的onMessage这个方法作为消息接收的方法
* @param messageReceiver 消息接受
* @return 适配器
*/
@Bean
public MessageListenerAdapter adapter(MessageReceiverSupport messageReceiver) {
return new MessageListenerAdapter(messageReceiver, "onMessage");
}
}
消息监听代码
/**
* @author yueyang
* @since 2021/02/05 14:48
**/
@Component
@Slf4j
public class MessageReceiverSupport implements MessageListener {
@Autowired
private RedisTemplate redisTemplate;
@Override
public void onMessage(Message message, byte[] pattern) {
RedisSerializer<String> redisSerializer = redisTemplate.getStringSerializer();
String msg= redisSerializer.deserialize(message.getBody());
System.out.println("接收到的消息是:"+ msg);
log.info("Received <" + msg + ">");
}
}
2.2发布代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
/**
* @author yueyang
* @since 2021/02/05 15:46
**/
@Service
public class RedisService {
@Autowired
RedisTemplate<String,String> redisTemplate;
public String sendMessage (String msg) {
try {
redisTemplate.convertAndSend(MessageConstant.AUDIT_LOG_CHANNEL, msg);
System.out.println(msg);
return "消息发送成功";
}catch (Exception e) {
e.printStackTrace();
return "消息发送失败";
}
}
}
2.3 nacos配置
spring:
redis:
host: 1.11.1.1
port: 6379
password: xxxx
lettuce:
pool:
max-active: 1000 #最大连接数(使用负值表示没有限制)
max-idle: 10 #最大空闲连接
min-idle: 5 #最新空闲连接
max-wait: -1 # 最大阻塞等待时间(使用负值表示没有限制)
3 问题
第一个原因是和redis系统的稳定性有关。对于旧版的redis来说,如果一个客户端订阅了某个或者某些频道,但是它读取消息的速度不够快,那么不断的积压的消息就会使得redis输出缓冲区的体积越来越大,这可能会导致redis的速度变慢,甚至直接崩溃。也可能会导致redis被操作系统强制杀死,甚至导致操作系统本身不可用。新版的redis不会出现这种问题,因为它会自动断开不符合client-output-buffer-limit pubsub配置选项要求的订阅客户端
第二个原因是和数据传输的可靠性有关。任何网络系统在执行操作时都可能会遇到断网的情况。而断线产生的连接错误通常会使得网络连接两端中的一端进行重新连接。如果客户端在执行订阅操作的过程中断线,那么客户端将会丢失在断线期间的消息,这在很多业务场景下是不可忍受的。
4 原理
发布订阅模式:就是一个发布者发布消息,多个订阅者进行消息的订阅,目的是为了消息的传送。
4.1 发布订阅模式的结构
主要包含三个部分:发布者,订阅者和Channel。
结合上图和消息中间件,可以将channel和消息中间件中的topic主题对应起来
发布订阅中使用到的命令就只有三个:PUBLISH,SUBSCRIBE,PSUBSCRIBE
- PUBLISH 用于发布消息
- SUBSCRIBE 也叫频道订阅,用于订阅某一特定的频道
- PSUBSCRIBE 也叫模式订阅,用于订阅某一组频道,使用glob的方式,比如xxx-*可以匹配xxx-a,和xxx-b,xxx-ddd等等
4.2 原理
Redis 订阅与发布 原理
client->pubsub_channels 是客户端维护的一个以dict结构的维护的订阅频道哈希表,VAL是NULL,不需要值。
server->pubsub_channels 是服务端维护的一个以dict结构的维护的订阅频道哈希表,VAL是以client维护的双向链表adlist。
一、订阅
订阅流程:SUBSCRIBE命令 SUBSCRIBE channel [channel ...]
1.首先是将当前订阅的频道channel添加进客户端的pubsub_channels哈希表里面。
2.然后在将当前订阅的频道channel和对应的client以键值对添加服务端的pubsub_channels的哈希表里。
3.最后将返回的信息返回给客户端。
二、发布
订阅流程:PUBLISH命令 PUBLISH channel message
1.首先通过频道在服务端的pubsub_channels哈希表里面找到对应的客户端链表。
2.然后递归循环链表,逐个将消息message发送对应订阅的客户端。
3.正则匹配的频道 逐个发送对应订阅的客户端。
参考链接:https://blog.csdn.net/luolaifa000/article/details/84110633