原创

微服务应用(十五):一台服务器重启导致 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+ 版本,支持属性配置项:

    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 配置项:

    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 拓扑刷新问题

正文到此结束
本文目录