錯誤:
原因:
SpringBoot2.X版本開始Redis默認的連接池都是采用的Lettuce。當節點發生改變后,Letture默認是不會刷新節點拓撲的。
方案一:把lettuce換成jedis
只需要在pom.xml里調整一下依賴的引用
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.1.5.RELEASE</version> <!-- 不用lettuce ,用jedis --> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0-m4</version> </dependency>
方案二:刷新節點拓撲視圖
redis節點異常,服務端的Redis集群拓撲被刷新了,java程序沒有獲取到新的拓撲。
Lettuce官方文檔中關於Redis Cluster的相關說明:Lettuce處理Moved和Ask永久重定向,由於命令重定向,你必須刷新節點拓撲視圖。而自適應拓撲刷新(Adaptive updates)與定時拓撲刷新(Periodic updates)是默認關閉的,可以通過如下代碼打開。
代碼如下:
package com.montnets.common.redis; import io.lettuce.core.ClientOptions; import io.lettuce.core.cluster.ClusterClientOptions; import io.lettuce.core.cluster.ClusterTopologyRefreshOptions; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author : harara * @version : 2.0 * @date : 2020/7/27 10:19 */ @Component public class RedisPoolConfig { @Autowired private RedisProperties redisProperties; public GenericObjectPoolConfig<?> genericObjectPoolConfig(RedisProperties.Pool properties) { GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>(); config.setMaxTotal(properties.getMaxActive()); config.setMaxIdle(properties.getMaxIdle()); config.setMinIdle(properties.getMinIdle()); if (properties.getTimeBetweenEvictionRuns() != null) { config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis()); } if (properties.getMaxWait() != null) { config.setMaxWaitMillis(properties.getMaxWait().toMillis()); } return config; } @Bean(destroyMethod = "destroy") public LettuceConnectionFactory lettuceConnectionFactory() { //開啟 自適應集群拓撲刷新和周期拓撲刷新 ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder() // 開啟全部自適應刷新 .enableAllAdaptiveRefreshTriggers() // 開啟自適應刷新,自適應刷新不開啟,Redis集群變更時將會導致連接異常 // 自適應刷新超時時間(默認30秒) .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30)) //默認關閉開啟后時間為30秒 // 開周期刷新 .enablePeriodicRefresh(Duration.ofSeconds(20)) // 默認關閉開啟后時間為60秒 ClusterTopologyRefreshOptions.DEFAULT_REFRESH_PERIOD 60 .enablePeriodicRefresh(Duration.ofSeconds(2)) = .enablePeriodicRefresh().refreshPeriod(Duration.ofSeconds(2)) .build(); // https://github.com/lettuce-io/lettuce-core/wiki/Client-Options ClientOptions clientOptions = ClusterClientOptions.builder() .topologyRefreshOptions(clusterTopologyRefreshOptions) .build(); LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() .poolConfig(genericObjectPoolConfig(redisProperties.getJedis().getPool())) //.readFrom(ReadFrom.MASTER_PREFERRED) .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); // lettuceConnectionFactory.setShareNativeConnection(false); //是否允許多個線程操作共用同一個緩存連接,默認true,false時每個操作都將開辟新的連接 // lettuceConnectionFactory.resetConnection(); // 重置底層共享連接, 在接下來的訪問時初始化 return lettuceConnectionFactory; } }
參考地址:
RedisCluster集群模式下master宕機主從切換期間Lettuce連接Redis無法使用報錯Redis command timed out的問題 : https://blog.csdn.net/u010046887/article/details/106948341