Redis集群宕机事故问题分析排查
计一次生产一台服务器重启导致 Redis 集群两个实例宕机服务不可用问题分析排查。
Redis 集群宕机导致签权服务异常,进而导致所有服务不可用,其中还包含支付系统,是一次严重的生产事故。
恢复耗时半小时,电话被打爆,高层领导,现场项目经理,销售,测试都紧盯着问题修复。
背景
系统是基于微服务架构开发部署,统一的签权中心服务,网关服务。
Redis 集群,签权中心,网关服务等微服务的基础组件是作为基础服务,给所有业务系统接入。
Redis 部署是在云服务器由运维搭建的 三主三从 集群。
微服务业务系统的框架统一使用的是基于 Spring Boot 2.2.6 版本集成好的基础框架开发。
问题
生产一台服务器重启导致 Redis 集群宕机所有业务服务不可用,一次严重的生产事故。
分析
服务器重启 Redis 实例没有重启。
原因:没有将 Redis 进程加入监控守护管理(Supervisor)。
解决:将 Redis 进程加入加入监控守护管理,系统重启后自动启动 Redis 进程。
一台服务重启,整个 Redis 集群不可用。
原因:Redis 集群是三主三从,存在一台服务器部署了两个 master 实例。
解决:增加服务器,三主三从 Redis 实例分别部署到单独的服务器上。
实验了主从切换后,出现业务系统连接失败。
原因:Spring Boot 默认使用的 Redis 客户端 Lettuce 没有动态刷新 Redis 集群节点的拓扑,当出现后仍一直连接其中一个节点导致超时。
解决:升级到 Spring Boot 2.3.0+ 版本,配置文件开启刷新 Redis 集群节点拓扑;不升级,手动添加刷新拓扑的 Class 配置。
Spring Boot 2.3.0+ 版本,支持属性配置项:
1
2
3
460s =
true|false =
60s =
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
66import 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封装
*
*/
public class RedisClusterTopologyRefreshBuilderCustomizer implements LettuceClientConfigurationBuilderCustomizer {
private LettuceClusterProperties lettuceClusterProperties;
private RedisProperties redisProperties;
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;
}
}参考: