Redis-cluster集群模式下,对于key的过期监听

近日,在开发项目过程中,开发环境是redis单机模式,测试环境是cluster集群模式,一份代码需要同时兼容开发环境与测试环境。

配置

首先,redis配置文件中修改notify-keyspace-events Ex,默认notify-keyspace-events "",不接收任何通知,然后重启redis

以下分别是单机和集群模式下的redis部分配置

单机:

cluster集群:

nodes为集群下的各个节点

RedisConfiguration加载

在网上看了些方案,有直接改造RedisMessageListenerContainer容器来实现对于集群节点的监听,这样对于实现在单机和集群模式下都能监听产生冲突,改造开销大,所以没有去研究。

所以在配置项方面,单机和集群对于RedisTemplate和RedisMessageListenerContainer初始化保持一致:

@Configuration
public class RedisConfiguration {
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        //key序列化
        redisTemplate.setKeySerializer(RedisSerializer.string());
        //value序列化
        redisTemplate.setValueSerializer(RedisSerializer.json());
        //value hashmap序列化
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        //key hashmap序列化
        redisTemplate.setHashValueSerializer(RedisSerializer.json());
        return redisTemplate;
    }

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        return container;
    }
}

redis单机模式下实现

网上有很多redis单机模式下实现监听的方法,这里直接上代码,创建一个类继承KeyExpirationEventMessageListener,重写onMessage方法即可。

@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {


    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    /**
     * 针对redis数据失效事件,进行数据处理
     *
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        //获得失效的key
        log.info("onMessage pattern {}, {}" ,Arrays.toString(pattern),message);
        //String msg = new String(pattern,StandardCharsets.UTF_8);
        String channel = new String(message.getBody());
        //业务逻辑处理....
    }
        
}

集群模式下实现监听

首先,实现每个节点对过期事件的订阅

@Slf4j
@Component
@RefreshScope
public class RedisClusterSubscriber extends RedisPubSubAdapter {
    //cluster集群下,每个节点只有db0,所以这边@0即可
    private static final String EXPIRED_CHANNEL = "__keyevent@0__:expired";

    @Value("${spring.redis.cluster.nodes:default_value}")
    private String nodes;

    @Value("${spring.redis.password:default_value}")
    private String password;

    private static final String defaultValue = "default_value";

    @Autowired
    private RedisKeyExpireClusterListener redisKeyExpireClusterListener;
    /**
     * 启动监听,异步订阅
     */
    @PostConstruct
    public void subscribe() {
        log.info("subscribe:{}",nodes);
        //这里用是否存在nodes节点信息区分是否需要走集群订阅模式
        if(!defaultValue.equals(nodes) && !defaultValue.equals(password)){
            log.info("redis集群环境");
            String[] nodesArr = nodes.split(",");
            List<RedisURI> REDIS_URIS = Arrays.asList(
                    RedisURI.create("redis://"+nodes[0])
                    //RedisURI.create("redis://"+nodes[1])
                    //.....

                    //这里只需要创建一个节点信息的连接即可,redis会根据节点信息获取集群的拓扑结构,不过为保险起见,可以将节点信息全部加载进来。
            );
            //转换为char数组
            char[] redisPass = password.toCharArray();

            for (RedisURI redisURI : REDIS_URIS){
                redisURI.setPassword(redisPass);
            }
            //创建客户端
            RedisClusterClient clusterClient = RedisClusterClient.create(REDIS_URIS);
            //尝试建立发布/订阅连接
            StatefulRedisClusterPubSubConnection<String, String> pubSubConnection = null;
            boolean isConnected = false;
            int maxRetries = 3; //最大重试次数
            int retryCount = 0;
            //连接失败的话,最多尝试三次连接
            while (!isConnected && retryCount<maxRetries) {
                try {
                    pubSubConnection = clusterClient.connectPubSub();
                    isConnected = true;
                } catch (Exception e) {
                    System.err.println("Failed to connect to Redis Cluster: " + e.getMessage());
                    retryCount++;
                    try {
                        Thread.sleep(1000); //等待1秒后重试
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    }
                }
            }

            if (isConnected) {
                //设置消息传播
                pubSubConnection.setNodeMessagePropagation(true);
                //添加监听器
                pubSubConnection.addListener(redisKeyExpireClusterListener);
                //选择所有节点
                final PubSubAsyncNodeSelection<String, String> all = pubSubConnection.async().all();
                final NodeSelectionPubSubAsyncCommands<String, String> commands = all.commands();
                //订阅事件
                commands.subscribe(EXPIRED_CHANNEL);
            } else {
                //所有重试均失败
                throw new RuntimeException("Failed to connect to Redis Cluster after " + maxRetries + " retries.");
            }
        }else{
            log.info("redis单机环境");
        }
    }
}

创建类继承‌RedisClusterPubSubAdapter适配器,重写message方法。RedisClusterPubSubAdapter用于实现Redis的发布(Pub)与订阅(Sub)功能,主要是为了在Redis集群环境中实现消息的发布和订阅,使得不同的客户端可以相互通信

@Slf4j
@Component
public class RedisKeyExpireClusterListener extends RedisClusterPubSubAdapter<String, String> {

    
    @Override
    public void message(RedisClusterNode node, String channel, String message) {
        log.info("redis集群监听--->节点:{}:{},channel:{},key:{}", node.getUri().getHost(), node.getUri().getPort(), channel,message);
        //业务逻辑....
    }
}

问题

这里无论是单机还是集群,若是在在多服务实例下,需要考虑key的重复消费问题,需要在实现监听的逻辑中,加上redis分布式锁来规避

总结

这里将集群与单机的代码区分开,利用同一个RedisMessageListenerContainer容器,实现在两种模式下对于集群和单机redis失效key的监听处理。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值