【故障演练】 Redis Cluster集群,当master宕机,主从切换,客户端报错 timed out

大家好,我是Tom哥

性能不够,缓存来凑

一个高并发系统肯定少不了缓存的身影,为了保证缓存服务的高可用,我们通常采用 Redis Cluster 集群模式。

【故障演练】 Redis Cluster集群,当master宕机,主从切换,客户端报错 timed out_第1张图片

描述:

集群部署采用了 3主3从 拓扑结构,数据读写访问master节点, slave节点负责备份。

随便登录一台 redis 节点,都可以看到集群的slot的槽位分步区间,以及对应的主从节点映射关系。

127.0.0.1:8001> cluster slots
1) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 8003
      3) "6c574c9d1323c69ebc73a5977bcbd3d4c073a4d4"
   4) 1) "127.0.0.1"
      2) (integer) 8006
      3) "123d0b157078925743ac1deb96be8c3395d7d038"
2) 1) (integer) 0
   2) (integer) 5460
   3) 1) "127.0.0.1"
      2) (integer) 8001
      3) "99bc05e81ef0035a4ab2d13cbae2599425b7ed7d"
   4) 1) "127.0.0.1"
      2) (integer) 8004
      3) "402e900ef364ce9382beddf92747cf28e3ea9c2f"
3) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 8002
      3) "fda6a9e49205a52418c0bca4c66c981066017a3c"
   4) 1) "127.0.0.1"
      2) (integer) 8005
      3) "24a1e23f6cbfb761234970b66043d562e79e3d9c"

人为模拟,master-1 机器意外宕机

docker stop c1dff012392d

此时,Redis Cluster 集群能自动感知,并自动完成主备切换,对应的slave会被选举为新的master节点

【故障演练】 Redis Cluster集群,当master宕机,主从切换,客户端报错 timed out_第2张图片

看下 redis cluster 集群最新的主从关系

【故障演练】 Redis Cluster集群,当master宕机,主从切换,客户端报错 timed out_第3张图片

看似也没什么问题,一切正常

此时 Spring Boot 应用依然在线服务,当我们再尝试操作缓存时,会报错

【故障演练】 Redis Cluster集群,当master宕机,主从切换,客户端报错 timed out_第4张图片

问题边界还是非常清晰的。

Redis Cluster 集群已经完成了切换。

但是 Spring Boot 客户端没有动态感知到 Redis Cluster 的最新集群信息

原因分析:

SpringBoot 2.X 版本, Redis默认的连接池采用 Lettuce

当Redis 集群节点发生变化后,Letture默认是不会刷新节点拓扑

解决方案:

Letture 二方包仲裁掉


    org.springframework.boot
    spring-boot-starter-data-redis
    2.3.12.RELEASE
    
        
            io.lettuce
            lettuce-core
        
    


然后,引入 Jedis 相关二方包


    redis.clients
    jedis
    3.7.0


编译代码,并重新启动 SpringBoot 微服务,万事俱备,只欠再次验证

【故障演练】 Redis Cluster集群,当master宕机,主从切换,客户端报错 timed out_第5张图片

重新模拟将 127.0.0.1:8001 master 节点宕机,看看系统的日志

[2022-03-17 18:03:34:595] - master /127.0.0.1:8001 used as slave
[2022-03-17 18:03:34:596] - slave redis://127.0.0.1:8004 removed for slot ranges: [[0-5460]]
[2022-03-17 18:03:34:611] - 1 connections initialized for /127.0.0.1:8004
[2022-03-17 18:03:34:639] - /127.0.0.1:8001 master and related slaves: [addr=redis://127.0.0.1:8004] removed
[2022-03-17 18:03:34:641] - 24 connections initialized for /127.0.0.1:8004
[2022-03-17 18:03:34:655] - 1 connections initialized for /127.0.0.1:8004
[2022-03-17 18:03:34:678] - master: redis://127.0.0.1:8004 added for slot ranges: [[0-5460]]
[2022-03-17 18:03:34:678] - 24 connections initialized for /127.0.0.1:8004

从打印的日志来看,客户端已经感知到了主备切换,并与最新的主节点 127.0.0.1:8004 初始化了 24 个连接。

然后,回归业务功能,读写缓存 数据也都是操作最新的主节点。

【故障演练】 Redis Cluster集群,当master宕机,主从切换,客户端报错 timed out_第6张图片

还有一种方案:刷新节点拓扑视图

Lettuce 官方描述:

https://github.com/lettuce-io/lettuce-core/wiki/Redis-Cluster#user-content-refreshing-the-cluster-topology-view

Lettuce 处理 Moved 和 Ask 永久重定向,由于命令重定向,必须刷新节点拓扑视图。而自适应拓扑刷新(Adaptive updates)与定时拓扑刷新(Periodic updates)默认关闭

解决方案:

  • 调用 RedisClusterClient.reloadPartitions

  • 后台基于时间间隔的周期刷新

  • 后台基于持续的断开移动、重定向 的自适应更新

编写代码

@Bean(destroyMethod = "destroy")
public LettuceConnectionFactory lettuceConnectionFactory() {

    //开启 自适应集群拓扑刷新和周期拓扑刷新
    ClusterTopologyRefreshOptions clusterTopologyRefreshOptions =  ClusterTopologyRefreshOptions.builder()
            // 开启自适应刷新。否则,Redis集群变更后将会导致连接异常
            .enableAllAdaptiveRefreshTriggers() 
            // 自适应刷新超时时间(默认30秒)
            .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30)) 
            // 开周期刷新
            .enablePeriodicRefresh(Duration.ofSeconds(20))  
            .build();

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

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

    List clusterNodes = redisProperties.getCluster().getNodes();
    Set nodes = new HashSet();
    clusterNodes.forEach(address -> nodes.add(new RedisNode(address.split(":")[0].trim(), Integer.valueOf(address.split(":")[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;
}


关于我:

Tom哥,计算机研究生,校招进阿里,期间还拿了百度、华为、中兴、腾讯 等6家大厂offer,P7 技术专家。出过专利,CSDN博客专家。

多年的大厂浸染,参加多次淘宝双11大促活动,在系统架构方面有丰富经验,沉淀总结在公众号:微观技术

他专注于微服务、高并发、高性能缓存、分布式架构、高可用、团队管理等方面,喜欢挖掘开源框架亮点设计,内容都是面试官喜欢考察的。强烈推荐关注一波。

你可能感兴趣的:(redis,缓存,宕机,redis,cluster,故障)