SpringBoot整合Redis之过期key监听

在缓存的使用场景中经常需要使用到过期事件,某些情况我们需要对缓存的过期事件进行监听并进行自己的操作,本文即为SpringBoot2.0整合Redis过期事件监听配置。

SpringBoot整合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区的数据,至于具体的表达式可以自行上网百度。

发布了43 篇原创文章 · 获赞 25 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/single_cong/article/details/103274667