SpringBoot整合 redis key (过期、新增、修改)的三种方式,看这篇就够了

2023年 9月 2日 63.2k 0

文章主要描述了Springboot整合key变化的三种方式,同时列出了一些整合坑点与概念

原理

SpringBoot整合Redis key变化的原理就是万变不离其宗,简单点就是:
spring-boot-starter-data-redis + notify-keyspace-events

关于 notify-keyspace-events

  • notify-keyspace-events AKEx 是 Redis 中的一个命令,用于配置服务器发送的通知类型。这里的参数 AKEx 代表的是键空间通知和键事件通知。

  • 具体来说,A 表示接收所有类型的通知,K 表示接收键空间通知,Ex 表示接收键事件通知。键空间通知是关于整个数据库的变化,而键事件通知是关于特定键的变化。

  • 举个例子,如果你在 Redis 中执行了 SET mykey "Hello" 命令,那么使用了 notify-keyspace-events AKEx 配置的客户端将会接收到一个关于 mykey 键的键事件通知。

  • 需要注意的是,要使用 notify-keyspace-events 命令,必须在 Redis 服务器中启用相关的 notify 机制,否则客户端无法接收到任何通知。同时,该命令只能在 Redis 的 string 类型键的值被修改时才会发送通知,其他类型键(如 hash、list、set、sorted set 等)的修改不会触发通知。

关于redis的消息主题(Topic)

Redis 消息主题(Topic)是用于发布和订阅消息的关键字。根据 Redis 的设计,它只支持发布/订阅模型的消息,而不支持请求/响应模型的消息。

在 Redis 中,可以使用 PSUBSCRIBE 命令来订阅一个或多个主题,并监听相关的消息。以下是一些常见的 Redis 消息主题:

  • __keyevent@*__:expired:过期事件主题,发布所有库过期消息,*表示所有。
  • __keyevent@0__:expired:过期事件主题,发布db0库过期消息0代表db0
  • __keyevent@1__:del:当使用 DEL 命令删除一个键时触发的事件。
  • __keyevent@4__:rename:当使用 RENAME 命令重命名一个键时触发的事件。
  • __keyevent@5__:set:当使用 SET 命令设置一个键的值时触发的事件。

这些主题是在 Redis 4.0 版本中引入的,用于表示键的特定事件。通过订阅相应的主题,可以监听相关的事件并进行相应的处理。
除了以上列举的主题外,Redis 还支持自定义的主题,用户可以根据自己的需求来定义和订阅相关的主题。

ok,在了解上面概念后我们直接上代码看下重写监听的方式吧

重写监听

  • 开启redis key变化事件
    redis配置文件配置 notify-keyspace-events AKEx ,默认是关闭的
  • 引入依赖(其他正常的依赖省略了,记得添加哈)
  • 
         org.springframework.boot
         spring-boot-starter-data-redis
     
    
  • 写个RedisListenerConfig 配置文件(文件名随便取,不一定要跟博主一样)
  • 
    
    @Configuration
    public class RedisListenerConfig {
    	//配置redis监听容器
        @Bean
        public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
            RedisMessageListenerContainer container = new RedisMessageListenerContainer();
            container.setConnectionFactory(connectionFactory);
            return container;
        }
        //配置redis的序列化策略
        
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate template = new RedisTemplate();
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);//所有属性均可见
            mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);//为null不参加序列化
            mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);//在Redis中存储对象类信息
            jackson2JsonRedisSerializer.setObjectMapper(mapper);
            template.setKeySerializer(stringRedisSerializer);
            template.setValueSerializer(jackson2JsonRedisSerializer);
            template.setHashKeySerializer(stringRedisSerializer);
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.setConnectionFactory(factory);
            return template;
        }
    }
    
  • 写个RedisKeyExpirationListener监听器
  • @Component
    @Slf4j
    public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    
        public RedisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) {
            super(redisMessageListenerContainer);
        }
    
        /**
         * 针对redis数据失效事件,进行数据处理
         * @param message message must not be {@literal null}. 过期的key
         * @param pattern pattern matching the channel (if specified) - can be {@literal null}. 队列名称
         */
        @Override
        public void onMessage(Message message, byte[] pattern) {
            // 拿到key
            String expiredKey = message.toString();
            log.info("监听Redis key过期,key:{},channel:{}", expiredKey, new String(pattern));
        }
    }
    

    到此就完成了key过期监听,那这种方式怎么做key更新和删除呢

    KeyExpirationEventMessageListener 该类是Springboot封装的过期监听类,我们看下源码。

    image.png

    但是Springboot可没有跟你封装更新和删除的哦。所以我们要学会举一反三。

    • 第一步复制KeyExpirationEventMessageListener,假设名字是KeyUpdateEventMessageListener
    • 修改__keyevent@0__:expired__keyevent@0__:set 这个是更新的topic
    • 写个RedisKeyUpdateListener extent KeyUpdateEventMessageListener,重写onMessage方法

    ok,写完了,插个tips。Python开发的文件类型转化windows工具,支持png,jpeg,ico等文件类型互转

    我们继续看下容器注册方式

    容器注册

    上面的配置就不重写了

  • 注意我们写个 RedisKeyUpdatedListener implements MessageListener
  • @Component
    @Slf4j
    public class RedisKeyUpdatedListener implements MessageListener {
    
        /**
         * key 更新监听
         */
        @Override
        public void onMessage(Message message, byte[] pattern) {
            // 拿到key
            String expiredKey = message.toString();
            log.info("监听Redis keyg更新,key:{},channel:{}", expiredKey, new String(pattern));
        }
    }
    
  • 然后修改下容器配置
  • @Bean
        public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
            RedisMessageListenerContainer container = new RedisMessageListenerContainer();
            container.setConnectionFactory(connectionFactory);
            //监听指定db0 的key set事件 *表示监听所有的db
            container.addMessageListener(redisKeyUpdatedListener, new PatternTopic("__keyevent@*__:set"));
            return container;
        }
    

    这里需要注意下redisKeyUpdatedListener 是通过注入方式注入的,不是new,因为如果通过new的方式。监听器有业务逻辑时会引入多个业务service组件。通过new的方式就只能通过构造的方式传入,而且service组件是从配置类注入的。

    多个的话如下

    container.addMessageListener(redisKeyUpdatedListener, new PatternTopic("__keyevent@*__:set"));
    container.addMessageListener(redisKeyExpiredListener, new PatternTopic("__keyevent@*__:expired"));
    container.addMessageListener(redisKeyDelListener, new PatternTopic("__keyevent@*__:del"));
    

    自定义解析

    这个就比较方便了。直接看代码吧,但耦合度有点高,不符合设计模式规范

    @Component
    public class CustomRedisMessageListener implements MessageListener {
        @Autowired
        private StringRedisTemplate redisTemplate;
        
        @Override
        public void onMessage(Message message, byte[] pattern) {
            String ops = new String(message.getBody());     //操作
            String channel = new String(message.getChannel());
            String key = channel.split(":")[1];
    		//对redis的新增或删除事件进行监听
            if ("set".equals(ops)) {
                String value = redisTemplate.opsForValue().get(key);
                handleSet(key, value);
            } else if ("del".equals(ops)) {
                handleDel(key);
            }
        }
    
        /**
         * 监听新增 处理逻辑
         */
        private void handleSet(String key, String value) {
    			//将数据同步刷新到内存中
                gatewayCache.refreshApiWhitelistsCache(id, JsonUtil.toObject(value, Set.class));
        }
    
        /**
         * 监听删除 处理逻辑
         * @param key 被删除的key
         */
        private void handleDel(String key) { 
                gatewayCache.deleteApiWhitelists(id);
        }
    }
    
    

    常见整合问题

    为啥我引入包,代码也无比的正确,为啥key过期了就是监听不到呢

    • 确保项目能启动
    • 确保依赖是否冲突了
    • 确保redis配置开启了 notify-keyspace-events AKEx

    为啥我配置了notify-keyspace-events 。就是没有监听到key 更新事件呢?

    • 确保配置的是 notify-keyspace-events AKEx,而不是 notify-keyspace-events Ex。ex只能监听到过期事件,而监听不到删除事件

    鸣谢

    • 非常感谢你从头到尾阅读了这篇文章,希望其中的内容对你有所启发和帮助。如果你还有其他问题或需要进一步的了解,欢迎随时关注我的动态并留言
    • 最后希望大家给作者点个关注和小赞赞支持下,创作不易啊
    • 觉得有收藏价值也可以进行收藏
    • 最后给大家来波小tips。优雅封装接口给第三方调用

    本文由mdnice多平台发布

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论