Redis集群宕机事故问题分析排查

计一次生产一台服务器重启导致 Redis 集群两个实例宕机服务不可用问题分析排查。

Redis 集群宕机导致签权服务异常,进而导致所有服务不可用,其中还包含支付系统,是一次严重的生产事故。

恢复耗时半小时,电话被打爆,高层领导,现场项目经理,销售,测试都紧盯着问题修复。

背景

  • 系统是基于微服务架构开发部署,统一的签权中心服务,网关服务。

    Redis 集群,签权中心,网关服务等微服务的基础组件是作为基础服务,给所有业务系统接入。

  • Redis 部署是在云服务器由运维搭建的 三主三从 集群。

  • 微服务业务系统的框架统一使用的是基于 Spring Boot 2.2.6 版本集成好的基础框架开发。

问题

生产一台服务器重启导致 Redis 集群宕机所有业务服务不可用,一次严重的生产事故。

分析

  1. 服务器重启 Redis 实例没有重启。

    原因:没有将 Redis 进程加入监控守护管理(Supervisor)。

    解决:将 Redis 进程加入加入监控守护管理,系统重启后自动启动 Redis 进程。

  2. 一台服务重启,整个 Redis 集群不可用。

    原因:Redis 集群是三主三从,存在一台服务器部署了两个 master 实例。

    解决:增加服务器,三主三从 Redis 实例分别部署到单独的服务器上。

  3. 实验了主从切换后,出现业务系统连接失败。

    原因:Spring Boot 默认使用的 Redis 客户端 Lettuce 没有动态刷新 Redis 集群节点的拓扑,当出现后仍一直连接其中一个节点导致超时。

    解决:升级到 Spring Boot 2.3.0+ 版本,配置文件开启刷新 Redis 集群节点拓扑;不升级,手动添加刷新拓扑的 Class 配置。

    Spring Boot 2.3.0+ 版本,支持属性配置项:

    1
    2
    3
    4
    spring.redis.timeout=60s
    spring.redis.lettuce.cluster.refresh.adaptive=true|false
    spring.redis.lettuce.cluster.refresh.period=60s
    spring.redis.lettuce.cluster.refresh.dynamic-refresh-sources=true|false //版本 2.4.0+ 增加

    Spring Boot 2.2.6 需要添加 Class 配置项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    import java.util.Objects;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
    import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
    import org.springframework.data.redis.core.RedisOperations;

    import com.clearofchina.core.util.ValidateUtil;
    import com.clearofchina.uaa.config.LettuceClusterProperties.Refresh;

    import io.lettuce.core.ClientOptions;
    import io.lettuce.core.ClientOptions.Builder;
    import io.lettuce.core.TimeoutOptions;
    import io.lettuce.core.cluster.ClusterClientOptions;
    import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;

    /**
    * Lettuce针对Redis Cluster的自定义配置,参照springboot2.3.7封装
    *
    */
    @Configuration
    @ConditionalOnClass(RedisOperations.class)
    @EnableConfigurationProperties(value = { RedisProperties.class, LettuceClusterProperties.class })
    public class RedisClusterTopologyRefreshBuilderCustomizer implements LettuceClientConfigurationBuilderCustomizer {

    @Autowired
    private LettuceClusterProperties lettuceClusterProperties;

    @Autowired
    private RedisProperties redisProperties;

    @Override
    public void customize(LettuceClientConfigurationBuilder clientConfigurationBuilder) {
    Builder topologyRefreshClientOptionsBuilder = initializeTopologyRefreshClientOptionsBuilder();
    if (Objects.nonNull(topologyRefreshClientOptionsBuilder)) {
    clientConfigurationBuilder.clientOptions(
    topologyRefreshClientOptionsBuilder.timeoutOptions(TimeoutOptions.enabled()).build());
    }
    }

    /**
    * 添加定时刷新拓扑选项
    *
    * @return
    */
    private ClientOptions.Builder initializeTopologyRefreshClientOptionsBuilder() {
    if (Objects.nonNull(redisProperties) && Objects.nonNull(redisProperties.getCluster())
    && ValidateUtil.isNotEmpty(redisProperties.getCluster().getNodes())) {
    ClusterClientOptions.Builder builder = ClusterClientOptions.builder();
    Refresh refreshProperties = lettuceClusterProperties.getRefresh();
    ClusterTopologyRefreshOptions.Builder refreshBuilder = ClusterTopologyRefreshOptions.builder();
    if (refreshProperties.getPeriod() != null) {
    refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());
    }
    if (refreshProperties.isAdaptive()) {
    refreshBuilder.enableAllAdaptiveRefreshTriggers();
    }
    return builder.topologyRefreshOptions(refreshBuilder.build());
    }
    return null;
    }
    }

    参考:

    Spring Boot 2.3.0 Redis拓扑动态感应

    SpringBoot2中使用Lettuce 拓扑刷新问题

作者

光星

发布于

2021-01-24

更新于

2022-07-12

许可协议

评论