众所周知,redis是一个高性能的分布式key-value存储系统,在NoSQL数据库市场上,redis自己就占据了将近半壁江山,足以见到其强大之处。同时,由于redis的单线程特性,我们可以将其用作为一个消息队列。本篇文章就来讲讲如何将redis整合到spring boot中,并用作消息队列的 (本人自己在线上使用后,感觉加分布式锁大大影响了效率,还没有单机效率高)
为什么会出现消息队列?
-
异步:常见的B/S架构下,客户端向服务器发送请求,但是服务器处理这个消息需要花费的时间很长的时间,如果客户端一直等待服务器处理完消息,会造成客户端的系统资源浪费;而使用消息队列后,服务器直接将消息推送到消息队列中,由专门的处理消息程序处理消息,这样客户端就不必花费大量时间等待服务器的响应了;
- 解耦:传统的软件开发模式,模块之间的调用是直接调用,这样的系统很不利于系统的扩展,同时,模块之间的相互调用,数据之间的共享问题也很大,每个模块都要时时刻刻考虑其他模块会不会挂了;使用消息队列以后,模块之间不直接调用,而是通过数据,且当某个模块挂了以后,数据仍旧会保存在消息队列中。最典型的就是生产者-消费者模式,本案例使用的就是该模式;
-
削峰填谷:某一时刻,系统的并发请求暴增,远远超过了系统的最大处理能力后,如果不做任何处理,系统会崩溃;使用消息队列以后,服务器把请求推送到消息队列中,由专门的处理消息程序以合理的速度消费消息,降低服务器的压力。
二、环境准备
Java环境:jdk1.8
spring boot版本:2.2.1.RELEASE
redis-server版本:3.2.100
三、相关依赖
这里只展示与redis相关的依赖,
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-redis</artifactId></dependency>
这里解释一下这两个依赖:
-
第一个依赖是对redis NoSQL的支持
- 第二个依赖是spring integration与redis的结合,这里添加这个代码主要是为了实现分布式锁
四、代码配置
redis用作消息队列,其在spring boot中的主要表现为一RedisTemplate.convertAndSend()方法和一个MessageListener接口。所以我们要在IOC容器中注入一个RedisTemplate和一个实现了MessageListener接口的类。话不多说,先看代码
配置RedisTemplate
配置RedisTemplate的主要目的是配置序列化方式以解决乱码问题,同时合理配置序列化方式还能降低一点性能开销。
/** * 配置RedisTemplate,解决乱码问题 */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { LOGGER.debug("redis序列化配置开始"); RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // string序列化方式 RedisSerializer serializer = new GenericJackson2JsonRedisSerializer(); // 设置默认序列化方式 template.setDefaultSerializer(serializer); template.setKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); LOGGER.debug("redis序列化配置结束"); return template; }
代码第12行,我们配置默认的序列化方式为GenericJackson2JsonRedisSerializer代码第13行,我们配置键的序列化方式为StringRedisSerializer代码第14行,我们配置哈希表的值的序列化方式为GenericJackson2JsonRedisSerializer
RedisTemplate几种序列化方式的简要介绍
生产者:
主要利用 redis的convertAndSend方法把数据生产。
@Autowired private StringRedisTemplate stringRedisTemplate; @Override public void sendMessage(String channel, String data) { stringRedisTemplate.convertAndSend(channel, data); }
消费者:
利用监听器监听某个方法,消费数据
/** *通过这个方法消费数据 * * * */ @Override public void receiveMessage(String id) { //业务逻辑 }
相关配置
package com.newtv.ucas.config; import com.newtv.ucas.dao.cache.impl.RedisDaoImpl; 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; /** * @Classname SubscriberConfig * @Description 作为消息队列的消费者 * @Date 2019/5/22 4:03 PM * @Author by pengasan */ @Configuration @AutoConfigureAfter({RedisDaoImpl.class}) public class SubscriberConfig { /** * 消息监听适配器,注入接受消息方法,输入方法名字 反射方法 * * @param redis * @return */ @Bean public MessageListenerAdapter getMessageListenerAdapter(RedisDaoImpl redis) { //当没有继承MessageListener时需要写方法名字 return new MessageListenerAdapter(redis,"receiveMessage"); } /** * 创建消息监听容器 * * @param redisConnectionFactory * @param messageListenerAdapter * @return */ @Bean public RedisMessageListenerContainer getRedisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory, MessageListenerAdapter messageListenerAdapter) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); redisMessageListenerContainer.addMessageListener(messageListenerAdapter,new PatternTopic("MessageNotify")); return redisMessageListenerContainer; } }
package com.newtv.ucas.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import org.springframework.integration.redis.util.RedisLockRegistry;/** * @Classname RedisCacheAutoConfiguration * @Description 注册redisTemplate,作为消息队列的发布者 * @Date 2019/5/22 3:37 PM * @Author by pengasan */@Configurationpublic class PublisherConfig extends RedisConfig{ @Value("${spring.redis.database}") private int database; @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.password}") private String password; @Bean public LettuceConnectionFactory secondConnectionFactory() { //Duration time = new return lettuceConnectionFactory( host, password, port, database); } @Bean(name = "redisTemplate") public RedisTemplate firstRedisTemplate() { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(secondConnectionFactory()); //key序列化 template.setKeySerializer(new StringRedisSerializer()); //value序列化 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } @Bean(name = "redisStringTemplate") public StringRedisTemplate firstStringRedisTemplate() { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(secondConnectionFactory()); template.afterPropertiesSet(); return template; } /** * redis分布书锁 bean 配置 * @Description 第一个参数是redis连接池,第二个参数是锁的前缀,即取出的锁,键名为“demo-lock:KEY_NAME”,第三个参数为锁的过期时间(秒),默认为60秒,当持有锁超过该时间后自动过期。 * @return org.springframework.integration.redis.util.RedisLockRegistry * @date 2019/12/2 3:41 PM * @auther lixin */ @Bean public RedisLockRegistry redisLockRegistry() { return new RedisLockRegistry(secondConnectionFactory(), "subscriber-lock",30); }}
我在使用中增加了分布式锁,效率没有单机快,于是不使用分布式锁了,继续单节点消费,你们是怎么处理的呢,欢迎交流