Redis Cluster集群,當Master宕機,主從切換,客戶端報錯 Timed Out

大家好,我是Tom哥。
性能不夠,緩存來湊。
一個高并發(fā)系統(tǒng)肯定少不了緩存的身影,為了保證緩存服務(wù)的高可用,我們通常采用 Redis Cluster 集群模式。

描述:
集群部署采用了 3主3從 拓撲結(jié)構(gòu),數(shù)據(jù)讀寫訪問master節(jié)點, slave節(jié)點負責備份。
隨便登錄一臺 redis 節(jié)點,都可以看到集群的slot的槽位分步區(qū)間,以及對應(yīng)的主從節(jié)點映射關(guān)系。
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 集群能自動感知,并自動完成主備切換,對應(yīng)的slave會被選舉為新的master節(jié)點。

看下 redis cluster 集群最新的主從關(guān)系。

看似也沒什么問題,一切正常。
此時 Spring Boot 應(yīng)用依然在線服務(wù),當我們再嘗試操作緩存時,會報錯。

問題邊界還是非常清晰的。
Redis Cluster 集群已經(jīng)完成了切換。
但是 Spring Boot 客戶端沒有動態(tài)感知到 Redis Cluster 的最新集群信息
原因分析:
SpringBoot 2.X 版本, Redis默認的連接池采用 Lettuce。
當Redis 集群節(jié)點發(fā)生變化后,Letture默認是不會刷新節(jié)點拓撲。
解決方案:
將 Letture 二方包仲裁掉。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.12.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
然后,引入 Jedis 相關(guān)二方包。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
編譯代碼,并重新啟動 SpringBoot 微服務(wù),萬事俱備,只欠再次驗證。
重新模擬將 127.0.0.1:8001 master 節(jié)點宕機,看看系統(tǒng)的日志。
[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
從打印的日志來看,客戶端已經(jīng)感知到了主備切換,并與最新的主節(jié)點 127.0.0.1:8004 初始化了 24 個連接。
然后,回歸業(yè)務(wù)功能,讀寫緩存 數(shù)據(jù)也都是操作最新的主節(jié)點。
還有一種方案:刷新節(jié)點拓撲視圖。
Lettuce 官方描述:
https://github.com/lettuce-io/lettuce-core/wiki/Redis-Cluster#user-content-refreshing-the-cluster-topology-view。
Lettuce 處理 Moved 和 Ask 永久重定向,由于命令重定向,必須刷新節(jié)點拓撲視圖。而自適應(yīng)拓撲刷新(Adaptive updates)與定時拓撲刷新(Periodic updates)默認關(guān)閉。
解決方案:
- 調(diào)用 RedisClusterClient.reloadPartitions。
 - 后臺基于時間間隔的周期刷新。
 - 后臺基于持續(xù)的斷開 和 移動、重定向 的自適應(yīng)更新。
 
編寫代碼
(destroyMethod = "destroy")
public LettuceConnectionFactory lettuceConnectionFactory() {
//開啟 自適應(yīng)集群拓撲刷新和周期拓撲刷新
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
// 開啟自適應(yīng)刷新。否則,Redis集群變更后將會導(dǎo)致連接異常
.enableAllAdaptiveRefreshTriggers()
// 自適應(yīng)刷新超時時間(默認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<String> clusterNodes = redisProperties.getCluster().getNodes();
Set<RedisNode> nodes = new HashSet<RedisNode>();
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 每個操作都將創(chuàng)建新的連接
// lettuceConnectionFactory.setShareNativeConnection(false);
// 重置底層共享連接, 在接下來的訪問時初始化
// lettuceConnectionFactory.resetConnection();
return lettuceConnectionFactory;
}















 
 
 




 
 
 
 