首页 > 数据库 >RedisCluster集群模式下master宕机主从切换期间Redis command timed out的问题

RedisCluster集群模式下master宕机主从切换期间Redis command timed out的问题

时间:2023-01-28 09:55:05浏览次数:65  
标签:String 宕机 redis Redis springframework command key org import

 

一、异常场景

如下表所示,redis集群6个节点分别部署在3台机器上,每台机器分布一个主节点和一个从节点

机器 redis主节点端口 redis从节点端口
主机1 7001 7004
主机2 7003 7006
主机3 7005 7002


将主机1关闭,redis集群发生主从切换,从节点7006升级为主节点,redis集群状态为ok

但是程序报如下错误:

 

二、原因分析

SpringBoot2.X版本开始Redis默认的连接池都是采用的Lettuce。当节点发生改变后,Letture默认是不会刷新节点拓扑的

 

三、解决方案

方案一:

使用letture方式连接redis,需要设置开启刷新节点拓扑

方案二:

改用jedis方式连接redis,使用jedis客户端的服务可以在主从切换后15秒恢复

 

方案1和方案2实现方式见 

RedisCluster集群模式下master宕机主从切换期间Lettuce连接Redis无法使用报错Redis command timed out的问题


以上两种方案在主从切换期间都有 短暂的时间 是程序不可用redis连接的,可能会导致程序业务数据丢失。方案三可解决此问题

 

方案三:

捕获程序中redis操作方法的连接异常,有异常就重新初始化连接工厂,直到连接可用

 

方案三实现如下:

1、Redis连接工厂配置类
package com.chenly.conf;

import cn.hutool.core.util.StrUtil;
import com.chenly.utils.TimeUtils;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.MapPropertySource;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * redis 配置类
 * @author  chenly
 * @date 2022/11/29 11:34
 * @version v1.0
 * @see {@link org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration}
 */
@Configuration
@Order(value = 1)
@Primary
public class RedisConfig {


    //最大活跃数
    @Value("${spring.redis.jedis.pool.max-active:8}")
    private int maxActive;

    //最大等待时间
    @Value("${spring.redis.jedis.pool.max-wait:-1ms}")
    private String maxWait;

    //最大核心线程数
    @Value("${spring.redis.jedis.pool.max-idle:8}")
    private int maxIdle;

    //最小核心线程数
    @Value("${spring.redis.jedis.pool.min-idle:0}")
    private int minIdle;

    //redis连接的超时时长
    @Value("${spring.redis.timeout:5}")
    private String timeOut;

    //redis连接的库
    @Value("${spring.redis.database:0}")
    private int database;
    //节点配置
    @Value("${spring.redis.cluster.nodes:#{null}}")
    private String nodes;

    //最大连接转移数
    @Value("${spring.redis.cluster.max-redirects:3}")
    private int maxRedirects;

    //单节点情况下redis的ip
    @Value("${spring.redis.host:#{null}}")
    private String host;

    //单节点情况下redis的端口
    @Value("${spring.redis.port:#{null}}")
    private Integer port;

    //redis的连接密码
    @Value("${spring.redis.password:#{null}}")
    private String password;


    public GenericObjectPoolConfig<?> genericObjectPoolConfig() {
        GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(maxActive);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setMaxWaitMillis(TimeUtils.parseUnitTimeToMillis(maxWait));
        return config;
    }


    /**
     * 连接配置
     * @return
     */
    public LettuceConnectionFactory   connectionFactory() {
        Map<String, Object> source = new HashMap<String, Object>();
        RedisClusterConfiguration redisClusterConfiguration;
        RedisStandaloneConfiguration redisStandaloneConfiguration;
        //开启 自适应集群拓扑刷新和周期拓扑刷新
        ClusterTopologyRefreshOptions clusterTopologyRefreshOptions =  ClusterTopologyRefreshOptions.builder()
                // 开启全部自适应刷新
                .enableAllAdaptiveRefreshTriggers() // 开启自适应刷新,自适应刷新不开启,Redis集群变更时将会导致连接异常
                // 自适应刷新超时时间(默认30秒)
                .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30)) //默认关闭开启后时间为30秒
                // 开周期刷新
                .enablePeriodicRefresh(Duration.ofSeconds(30))  // 默认关闭开启后时间为60秒 ClusterTopologyRefreshOptions.DEFAULT_REFRESH_PERIOD 60  .enablePeriodicRefresh(Duration.ofSeconds(2)) = .enablePeriodicRefresh().refreshPeriod(Duration.ofSeconds(2))
                .build();
        // https://github.com/lettuce-io/lettuce-core/wiki/Client-Options
        ClientOptions clientOptions = ClusterClientOptions.builder()
                //.topologyRefreshOptions(clusterTopologyRefreshOptions)
                //redis命令超时时间,超时后才会使用新的拓扑信息重新建立连接
                //.timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(10)))
                .build();

//        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
//                .clientOptions(clientOptions)
//                .build();

        LettuceClientConfiguration clientConfig= LettucePoolingClientConfiguration.builder()
                .poolConfig(genericObjectPoolConfig())
                .clientOptions(clientOptions)
                .commandTimeout(Duration.ofMillis(TimeUtils.parseUnitTimeToMillis(timeOut))) //默认RedisURI.DEFAULT_TIMEOUT 60
                .build();



        //集群模式
        if(nodes !=null){
            source.put("spring.redis.cluster.nodes", nodes);
            source.put("spring.redis.cluster.max-redirects", maxRedirects);
            redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", source));
            if(!StrUtil.isEmpty(password)){
                redisClusterConfiguration.setPassword(password);
            }
            //根据配置和客户端配置创建连接工厂
            LettuceConnectionFactory lettuceConnectionFactory = new
                    LettuceConnectionFactory(redisClusterConfiguration,clientConfig);
            return lettuceConnectionFactory;
        }else{
            //单机模式
            redisStandaloneConfiguration = new RedisStandaloneConfiguration(host,port);
            redisStandaloneConfiguration.setDatabase(database);
            if(!StrUtil.isEmpty(password)){
                redisStandaloneConfiguration.setPassword(password);
            }
            //根据配置和客户端配置创建连接
            LettuceConnectionFactory lettuceConnectionFactory = new
                    LettuceConnectionFactory(redisStandaloneConfiguration,clientConfig);
//            lettuceConnectionFactory .afterPropertiesSet();
            return lettuceConnectionFactory;
        }
    }
}

 
 2、函数式接口

 

package com.chenly.utils;

/**
 * @author: chenly
 * @date: 2022-11-29 16:55
 * @description:
 * @version: 1.0
 */
@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法
    public abstract Object method();
}
 3、redis操作类
package com.montnets.emp.utils;

import com.montnets.emp.conf.RedisConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
  * @初创作者: chenly
 * @创建时间: 2022/11/29 15:51
 */
@Component
@Slf4j
public class RedisUtil {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedisConfig redisConfig;

    //重试次数
    @Value(value = "${ums.redis.retry.times:10}")
    private int retryCount = 10;
    //重试休眠时间,单位:毫秒
    private int retrySleepTime = 3000;

    /**
     * @author :chenly
     * 更新redisTemplate :处理集群宕机恢复后程序不恢复问题
     */
    private synchronized StringRedisTemplate refreshRedisTemplate() {
        LettuceConnectionFactory connectionFactory = redisConfig.connectionFactory();
        connectionFactory.afterPropertiesSet();
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }

    /**

*   redis操作
     *  使用函数式接口,
     * @author :chenly
     * @param myInter 函数式接口
     * @param logContent 日志描述
     * @return
     */

    public Object redisOperation(MyFunctionalInterface myInter,String logContent){
        try{
            return myInter.method();
        }catch (Exception e){
            log.warn(logContent,e.getMessage());
            return redisRetry(myInter,logContent);
        }
    }
 

/**
     * 异常重试

* 使用函数式接口
     * @author :chenly
     * @param myInter 函数式接口
     * @param logContent 日志描述
     * @return
     */

    public Object redisRetry(MyFunctionalInterface myInter, String logContent){
        Object object = null;
        for(int i=0;i <=retryCount;i++){
            synchronized (this) {
                try {
                     object =  myInter.method();
                     log.info("第{}次重试: "+logContent,(i+1),"成功");
                     return object;
                } catch (Exception e) {
                    if (i >= retryCount) {
                        log.error("第{}次重试: "+logContent, (i+1),"异常:"+e.getMessage());
                        throw e;
                    }
                    log.warn("第{}次重试: "+logContent,(i+1), "异常:"+e.getMessage());
                    //更新redisTemplate
                    redisTemplate = refreshRedisTemplate();
                }
            }
            try {
                TimeUnit.MILLISECONDS.sleep(retrySleepTime);
            } catch (InterruptedException e1) {
                log.warn("休眠异常", e1.getMessage());
            }
        }
        return null;

    }
    /**
     * 删除key
     * 使用函数式接口
     * @param key
     */
    public void delete(String key) {
        String logContent = "redis操作: key:" + key + ", delete执行结果:{}";
        redisOperation(() -> {
            redisTemplate.delete(key);
            return null;
        }, logContent);
    }

    /**
     * 设置指定 key 的值
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        String logContent = "【redis】set操作: key:"+key+", value:"+value+",执行结果:{}";
        redisOperation(()->{
            redisTemplate.opsForValue().set(key, value);
            return null;
        },logContent);


    }

    /**
     * 获取指定 key 的值
     * @param key
     * @return
     */
    public String get(String key) {
        String logContent = "【redis】get操作: key:"+key+",执行结果:{}";
        return (String) redisOperation(()->{
            return redisTemplate.opsForValue().get(key);
        },logContent);

    }

}

 

4、测试结果:

redis集群主从切换期间,程序有redis操作,会异常重试2~3次,即可恢复正常使用

标签:String,宕机,redis,Redis,springframework,command,key,org,import
From: https://www.cnblogs.com/kiko2014551511/p/17044494.html

相关文章

  • 学习笔记——redis数据类型(ZSet)
    2023-01-27一、redis数据类型(ZSet)redis中的zset是一个有序集合,是一个没有重复元素的字符串集合。注意:①zset中的每个成员都关联了一个评分,这个评分是从最低分到最高分的......
  • 学习笔记——redis中的数据类型(List、Set、Hash)
    2023-01-25一、redis中的数据类型1、redis列表(List)redis列表底层是一个双向链表。(1)从左边/右边插入一个或多个值lpush/rpush<key><value1><value2><value3>例如:......
  • bash: redis-server: 未找到命令...
    2023-01-27今天使用xshell连接linux中的redis数据库时,出现的该问题“ bash:redis-server:未找到命令...”使用redis-server/root/myredis/redis.conf命令,连接数......
  • redis 缓存引发的头疼问题
    缓存穿透某个key缓存没有,数据库也没有。一般这种情况发生了用户恶意请求或者攻击。造成一直不停查库解决方案最顶层拦截,不合理的id直接打回去或者布隆过滤器db如果差不多,......
  • Redis实现全局唯一id
    全局Id生成器为了增加id的安全性,我们可以不直接使用redis自增生成的数值,可以拼接一些其他的数值id的组成部分符号位:1bit,永远为0时间戳:31bit,以秒为单位,可以使用69年......
  • Redis学习笔记
    1.简介概述Redis是基于内存的key-value数据库基于内存存储,独写性能高,所有Redis很多时候会作为缓存来使用适合存储热点数据:短时间有大量用户访问MySQL则是存在磁盘......
  • go 使用redis
       import(redigo"github.com/gomodule/redigo/redis")  funcNewPool()*redigo.Pool{//return&redigo.Pool{//MaxIdle:3,//IdleTimeou......
  • 微服务 Spring Boot 整合 Redis BitMap 实现 签到与统计
    文章目录​​⛄引言​​​​一、RedisBitMap基本用法​​​​⛅BitMap基本语法、指令​​​​⚡使用BitMap完成功能实现​​​​二、SpringBoot整合Redis实现签到......
  • idea中使用插件操作mysql、redis
    idea中使用插件操作mysqlidea右侧选择Database 输入连接信息 可以写sql并执行  idea中使用redis插件操作redis安装插件 安装“Redis” 输入连接......
  • 学习笔记——NoSQL数据库;Redis概述;redis中常用的数据类型(key、string)
    2023-01-24一、NoSQL数据库1、NoSQL数据库的简介NoSQL(NoSQL=NotOnlySQL),即“不仅仅是SQL”,泛指非关系型的数据库。NosQL不依赖业务逻辑方式存储,而以简单的key-value模......