在缓存的使用场景中经常需要使用到过期事件,某些情况我们需要对缓存的过期事件进行监听并进行自己的操作,本文即为SpringBoot2.0整合Redis过期事件监听配置。
-
修改缓存参数
修改缓存的conf文件,设置参数notify-keyspace-events “Ex”,默认是无参数的,将参数设置为Ex即可。 -
缓存配置
在配置文件中进行缓存连接参数配置此处略过。
缓存序列化配置如下:package org.pet.king.config; import java.sql.SQLException; import java.time.Duration; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; /** * 缓存Redis序列化配置 * * @author single-聪 * @date 2019年7月15日 * @version 0.0.1 */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { /** * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类 * * @param lettuceConnectionFactory * @return */ @Bean public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { // // 解决每次传入的都是一个lettuceConnectionFactory实际上使用同一个分区问题 // RedisStandaloneConfiguration redisStandaloneConfiguration = // lettuceConnectionFactory // .getStandaloneConfiguration(); // LettuceClientConfiguration lettuceClientConfiguration = // lettuceConnectionFactory.getClientConfiguration(); // // 默认分区为0 // redisStandaloneConfiguration.setDatabase(0); // LettuceConnectionFactory connectionFactory = new // LettuceConnectionFactory(redisStandaloneConfiguration, // lettuceClientConfiguration); // connectionFactory.afterPropertiesSet(); RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); // 使用Jackson2JsonRedisSerialize 替换默认序列化 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>( Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 设置value的序列化规则和 key的序列化规则 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // hash参数序列化方式 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // 缓存支持回滚(事务管理) redisTemplate.setEnableTransactionSupport(true); redisTemplate.setConnectionFactory(lettuceConnectionFactory); redisTemplate.afterPropertiesSet(); return redisTemplate; } // 配置事务管理器 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) throws SQLException { return new DataSourceTransactionManager(dataSource); }
我一般使用的序列化方式都为Jackson2JsonRedisSerializer,当然你可以根据自己的需要设置自己的序列化方式。缓存的序列化配置在这里就结束了,但是需要注意一点(很多人应该使用不到):
代码中注释的那部分代码,本意是我想让不同的redisTemplate使用不同的分区(默认是db0),但是当我配置了几个redisTemplate之后发现所有的redisTemplate使用的分区是最终的那个分区(按照加载顺序最终加载的是哪个分区所有的redisTemplate就使用哪个分区),配置这个的目的是:缓存中基本所有数据都会过期,但是我只想要监听我需要做数据处理的那一部分过期key,其他的监听了没意义还要做逻辑判断浪费资源,但是我发现一个服务中没法随意切换db分区(将服务分开或许是一种解决方式,将自己想要监听的过期key全部放入db(n)中),注意一个服务中虽然不能随意切换,但是可以切换!!但是切换之后所有的redisTemplate操作的分区就会变成新切换的分区,易造成前面加入缓存中的数据查询不到的情况(慎用!!!!) -
实现MessageListener接口
package org.pet.king.config; import org.pet.king.entity.MessageCollect; import org.pet.king.entity.MessageFans; import org.pet.king.entity.Publish; import org.pet.king.entity.UserShow; import org.pet.king.service.PetService; import org.pet.king.service.PublishService; import org.pet.king.service.UserService; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; /** * Redis缓存key过期监听 * * @author single-聪 * @date 2019年11月22日 * @version 0.0.1 */ @Slf4j @Component public class RedisExpireListener implements MessageListener { private RedisTemplate<Object, Object> redisTemplate; private UserService userService; @Override public void onMessage(Message message, byte[] pattern) { log.info("监听key过期事件[{}]...[{}]", message, pattern); String expireKey = message.toString(); if (expireKey.startsWith("ip")) { // IP数据过期事件过多,第一个就将其排除,不进行后续操作 return; } else { log.info("根据key过期前缀调用相应接口"); } } public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) { this.redisTemplate = redisTemplate; } public void setUserService(UserService userService) { this.userService = userService; } }
这里需要注意一点,在这个实现类里面不能进行@Autowired或者@Resource注入,在这个类会注入失败,导致你调用这个接口的方法的时候会报空指针异常(实际上是userService未注入,它是null,而不是参数为null),所以我这里使用的是私有属性,通过set方法设置,设置的地方在下面这个类中。
-
配置缓存过期监听
package org.pet.king.config; import org.pet.king.service.PetService; import org.pet.king.service.PublishService; import org.pet.king.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; @Configuration public class RedisMessageListener { @Autowired private RedisConnectionFactory redisConnectionFactory; @Autowired private RedisTemplate<Object, Object> redisTemplate; @Autowired private UserService userService; @Bean public RedisMessageListenerContainer redisMessageListenerContainer() { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory); // 解决bean注入失败问题 RedisExpireListener redisExpireListener = new RedisExpireListener(); redisExpireListener.setRedisTemplate(redisTemplate); redisExpireListener.setUserService(userService); // 监听缓存key过期 container.addMessageListener(redisExpireListener, new ChannelTopic("__keyevent@0__:expired")); return container; } }
上述配置即可监听缓存db0区的数据,至于具体的表达式可以自行上网百度。