Springboot集成Lettuce完成Redis cluster集群的key过期监控

       经过几天各种方案的对比及实验,终于完成了Springboot集成Lettuce完成Redis cluster集群的key过期监控的代码,主要参考了如下的文章及代码:

https://my.oschina.net/u/4134799/blog/3116221/print

https://github.com/xfearless1201/api/blob/master/src/main/java/com/cn/tianxia/api/base/redis/RedisMessageListenerFactory.java

       前提条件是需要在redis的配置文件里把这个notify-keyspace-events Ex过期监控加上,我们首先要了解在Redis cluster集群中没有了多数据库的概念,只有database0,因此在监控topic的时候一定要是__keyevent@0__:expired的过期监控,否则是不会起作用的。

       因为以前在使用springboot+jedis的时候是不需要考虑到主从切换时没有成功的情况的,因为它会自动每隔15秒左右就会进行自动刷新,但是切换到lettuce的时候就不一样了,它要求用户自己去定时刷新cluster节点的信息,因此需要如下配置

@Resource
    private RedisProperties redisProperties;

@Bean
    public LettuceConnectionFactory lettuceConnectionFactory(){
        // 开启自适应集群拓扑刷新和周期拓扑刷新
        ClusterTopologyRefreshOptions refreshOptions = ClusterTopologyRefreshOptions.builder()
                // 开启全部自适应刷新,自适应刷新不开启,Redis集群变更时将会导致连接异常
                .enableAllAdaptiveRefreshTriggers()
                // 自适应刷新超时时间(默认30秒)
                .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))
                // 开启周期刷新,默认关闭,开启后时间默认为60秒
                .enablePeriodicRefresh(Duration.ofSeconds(10))
                .build();

        ClientOptions clientOptions = ClusterClientOptions.builder()
                .topologyRefreshOptions(refreshOptions)
                .build();

        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .poolConfig(genericObjectPoolConfig(redisProperties))
                //.readFrom(ReadFrom.MASTER_PREFERRED)
                .clientOptions(clientOptions)
                // 默认RedisURI.DEFAULT_TIMEOUT 60
                .commandTimeout(redisProperties.getTimeout())
                .build();

        List<String> clusterNodes = redisProperties.getCluster().getNodes();
        Set<RedisNode> nodes = new HashSet();
        clusterNodes.forEach(address -> {
            String[] ipPort = address.split(":");
            nodes.add(new RedisNode(ipPort[0].trim(), Integer.valueOf(ipPort[1])));
        });

        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
        clusterConfiguration.setClusterNodes(nodes);
        //clusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        clusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(clusterConfiguration, clientConfig);
        // 是否允许多个线程操作共用同一个缓存连接,默认true,false时每个操作都将开辟新的连接
        // lettuceConnectionFactory.setShareNativeConnection(false);
        // 重置底层共享连接, 在接下来的访问时初始化
        // lettuceConnectionFactory.resetConnection();
        return lettuceConnectionFactory;
    }

@Bean
    public GenericObjectPoolConfig<?> genericObjectPoolConfig(RedisProperties redisProperties) {
        GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
        RedisProperties.Pool properties = redisProperties.getLettuce().getPool();
        config.setMaxTotal(properties.getMaxActive());
        config.setMaxIdle(properties.getMaxIdle());
        config.setMinIdle(properties.getMinIdle());
        if (properties.getMaxWait() != null) {
            config.setMaxWaitMillis(properties.getMaxWait().toMillis());
        }
        return config;
    }

        lettuce是springboot默认的引入,通过上面的配置信息,在集群节点发生改变时,它会去主动的定时刷新集群中redis的状态,接下来我们就需要一个定时任务来获取Redis集群中的所有master节点,从而做到master节点中的key值过期时可以获取到过期的key,为什么只是监听master节点是因为slave节点只是做个备份,不发生任何和变化操作相关的操作,看下定时监听代码

@Component
public class RedisMessageRefreshClusterNodeFactory {

    @Resource
    private RedisConnectionFactory redisConnectionFactory;

    @Resource
    private RedisExpiredMessageListener messageListener;

    @Resource
    private ListenerSetting listenerSetting;

    /**
     * 用于存储已经启动监听的master节点信息
     */
    ConcurrentHashMap masterNodeMap = new ConcurrentHashMap();

    @Scheduled(cron = "0/5 * * * * ?")
    public void refreshClusterNode() {
        RedisClusterConnection clusterConnection = redisConnectionFactory.getClusterConnection();
        if (Optional.ofNullable(clusterConnection).isPresent()) {
            /*
                结束了旧的master节点后,旧的节点为master节点的状态不会改变,
                只有重启旧的master节点后变为了slave节点获取到的状态才会发生变化
            */
            Iterable<RedisClusterNode> redisClusterNodes = clusterConnection.clusterGetNodes();

            // 用于获取当前的master节点信息
            HashMap currentMasterNodeMap = new HashMap();
            redisClusterNodes.forEach(redisClusterNode -> {
                if (redisClusterNode.isMaster()) {
                    String clusterNodeName = "clusterNodeName" + redisClusterNode.hashCode();
                    String hostPort = redisClusterNode.getHost() + ":" + redisClusterNode.getPort();
                    currentMasterNodeMap.put(clusterNodeName, hostPort);
                    // 不进行重复创建监听
                    if (masterNodeMap.containsKey(clusterNodeName)) {
                        return;
                    }
                    masterNodeMap.put(clusterNodeName, hostPort);

                    RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisClusterNode.getHost(), redisClusterNode.getPort());
                    LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
                    connectionFactory.afterPropertiesSet();

                    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
                    container.setConnectionFactory(connectionFactory);
                    // 设置监听使用的线程池
                    container.setTaskExecutor(ExecutorUtil.executorService);
                    // 设置监听的Topic
                    ChannelTopic channelTopic = new ChannelTopic(listenerSetting.getMerchantTopicExpired());
                    // 设置监听器
                    container.addMessageListener(messageListener, channelTopic);
                    // 必须调用,否则空指针异常
                    container.afterPropertiesSet();
                    // 启动监控
                    container.start();
                }
            });

            // 比较2个map中的值,去除不存在的值
            masterNodeMap.forEach((key, value) -> {
                if (!currentMasterNodeMap.containsKey(key)) {
                    masterNodeMap.remove(key);
                }
            });

        }
    }

}

       需要注意的是要启动定时任务,必须在启动类上面添加@EnableScheduling注解,那么剩下的listener类就如下

@Component
@Slf4j
public class RedisExpiredMessageListener implements MessageListener {


    @Override
    public void onMessage(Message message, byte[] bytes) {
        // 过期的key
        String expiredKey = new String(message.getBody());
        //对监听消息进行处理
        if (expiredKey.contains("")) {

        }
    }

}

 

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot项目中,有三种方式可以集成Redis集群,包括使用Jedis、LettuceRedisTemplate。 对于Jedis的整合步骤,你需要先导入相应的依赖,包括spring-boot-starter-data-redislettuce-core。然后,你可以通过配置类来修改Swagger配置,以便适应你的项目需求。具体的代码实现可以参考上面给出的示例。 对于Lettuce的整合步骤,你同样需要导入相应的依赖,包括spring-boot-starter-data-redislettuce-core。然后,你可以在配置类中根据需要修改Swagger配置。具体的代码实现可以参考上面给出的示例。 对于RedisTemplate的整合步骤,你同样需要导入相应的依赖,包括spring-boot-starter-data-redislettuce-core。然后,你可以在配置类中根据需要修改Swagger配置。具体的代码实现可以参考上面给出的示例。 总结来说,不论是使用Jedis、Lettuce还是RedisTemplate,你都需要导入相应的依赖并进行相应的配置,以便在Spring Boot项目中实现Redis集群的整合。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [springboot整合redis集群](https://blog.csdn.net/m0_68574821/article/details/129899360)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [SpringBoot整合Redis](https://blog.csdn.net/l_zl2021/article/details/129368515)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值