redis在项目中实战

在之前的博客中我们熟悉了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)消息。发版结束,将标志位关闭,继续消费。

题后话,由于作者水平有限,文章中难免会有歧义的地方,欢迎大家批评指正,作者定会及时改正

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值