在之前的博客中我们熟悉了redis是什么以及基础的命令行操作,那么在生产中我们是要在java代码中去操作redis,那要怎么做呢,直接上代码:
springboot整合redis实战配置(其中springboot版本:1.5.9.RELEASE,2.x的支持lettuce配置,redis版本:4.0.11):
pom:
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 解决LocalDate 序列化问题 所需jar-->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
yml,根据实际自行修改:
redis:
database: 0
host: localhost
password: 123
port: 6379
pool:
max-active: 1000
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 0
新建redis的配置类:
@Configuration
public class RedisConfig {
// 以下两种redisTemplate自由根据场景选择
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 使用Jackson2JsonRedisSerializer做序列化方案,默认是JDK
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule());
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(factory);
return stringRedisTemplate;
}
}
解释一下为什么会要用到 jackson-datatype-jsr310 这个jar:
当使用Jackson2JsonRedisSerializer 进行redis的value序列化的时候,就像这样:
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 设置value的序列化规则和 key的序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
那么恭喜了,如果实体中有LocalDate 这样的java8的日期对象,序列化之后redis中存的值会是这样:
"date": {
"year": 2019,
"month": "FEBRUARY",
"day": 27,
"dayOfMonth": 27,
"dayOfWeek": "WEDNESDAY",
"era": [
"java.time.chrono.IsoEra",
"CE"
],
"dayOfYear": 58,
"leapYear": false,
"chronology": {
"id": "ISO",
"calendarType": "iso8601"
},
"prolepticMonth": 24229,
"monthValue": 2
},
"time": {
"hour": 12,
"minute": 10,
"second": 17,
"nano": 0
},
"month": "FEBRUARY",
"year": 2019,
"dayOfMonth": 27,
"dayOfWeek": "WEDNESDAY",
"dayOfYear": 58,
"hour": 12,
"minute": 10,
"nano": 0,
"second": 17,
"monthValue": 2,
"chronology": [
"java.time.chrono.IsoChronology",
{
"id": "ISO",
"calendarType": "iso8601"
}
]
}
这种格式从redis中get出来进行反序列化会抛异常:
解决办法就是配置Jackson2JsonRedisSerializer的时候加俩属性并加入pom依赖:
/解决LocalDate 序列化问题
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
//pom:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
这样在redis中存的就是:
"expireTime": "2019-07-22T15:53:32.976"
上面的redis配置类已经添加了这个配置
接下来就是实际的编码了:
//注入操作redis的模板工具
@Autowired
RedisTemplate redisTemplate;
//这个工具中封装了所有类型的操作方法,下面举例:
//string
redisTemplate.opsForValue();
//list
redisTemplate.opsForList();
//set
redisTemplate.opsForSet();
//zset
redisTemplate.opsForZSet();
//hash
redisTemplate.opsForHash();
//简单介绍一个使用的方法,和命令行操作redis的指令基本类似,小伙伴们看一看api就可以了。
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.get("key");
valueOperations.set("key", "value");
redis实现消息队列
redis也可以当作mq来使用虽然不推荐这样。它的核心思想就是利用redis中的list,只需要两个命令,lpush/rpop,为了保证消息可用,也可以设计一级失败队列和二级失败队列(死信队列),直接上代码:
//声明一个线程池,用来执行消息的业务逻辑。
@Configuration
public class RedisTask {
@Bean("redisTaskExecutor")
public ThreadPoolTaskExecutor redisTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setThreadNamePrefix("redis-queue-");
return executor;
}
}
//注入操作redis的模板工具
@Autowired
StringRedisTemplate stringRedisTemplate;
//执行线程池
@Autowired
ThreadPoolTaskExecutor redisTaskExecutor;
// 生产者,向队列中放消息
@Test
public void producer(){
ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();
//向redis中放入一个key:queueAddress,是list类型。value为messageContent
listOperations.leftPush("queueAddress", "messageContent");
)
//消费者,从队列中拿消息。
@Test
public void consumer(){
while(true){
//阻塞式的pop,list中无数据的时候将阻塞,参数0表示一直阻塞下去,直到list中出现数据
ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();
String message = listOperations.rightPop("queueAddress", 0, TimeUnit.MINUTES);
//这里可以用线程池来执行具体的业务逻辑。
redisTaskExecutor.getThreadPoolExecutor().execute(()->{
System.out.println("收到消息:" + message);
//执行业务逻辑。。。
})
}
}
到现在正常版的redis队列(queue模式)就实现了,但是上面说到为了保证消息可用,我们可以把消费消息的业务逻辑try-catch 包裹,如果抛出异常就将该消息放到一级失败队列中,这个失败队列可以放在一个定时任务中,比如每天的晚上12点 定时执行这些失败的队列消息,如果还是失败就放到二级队列中,这个队列的消息需要手动处理。
不知道小伙伴有没有注意到这还是有一个问题的,那就是项目发版的时候,如果当前服务正在消费消息,服务停止,那么就会在消费端丢失了消息。且有概率会出现消息的业务逻辑执行一半的可能。我们可以在redis中做个标志位,当要发版的时候,将标志位打开,我们在消息消费端实时监测这个标志位,发现打开了就不再继续消费(pop)消息。发版结束,将标志位关闭,继续消费。
题后话,由于作者水平有限,文章中难免会有歧义的地方,欢迎大家批评指正,作者定会及时改正