背景
第一种方案:
公司项目做性能测试,redis集群选用cluster集群模式,开始选用3主3从,共3台服务器,每个服务器一主一从。
服务器A redis 8001 8002
服务器B redis 8003 8004
服务器C redis 8005 8006
8001 主
8002 从 (主为8003)
8003 主
8004 从(主为8005)
8005 主
8006从(主为8001)
1.停止服务器A
此时集群状态为
8003 主
8004 从(主为8005)
8005 主
8006 主(代替8001)
2.启动服务器A
此时集群状态为
8001 从 (主为8006)
8002 从 (主为8003)
8003 主
8004 从(主为8005)
8005 主
8006 主
3.停止服务器C
由于C服务器现承载两个主节点,服务器停止 导致集群过半数主节点停止,集群瘫痪
总结 :此方案在业内较为流行,但不是最完美的解决方案,若遇到此种情况 无法避免
第二种方案:
服务器A redis 8001
服务器B redis 8002
服务器C redis 8003
服务器D redis 8004
服务器E redis 8005
服务器F redis 8006
随意启停服务均可实现高可用,但是此时又暴露出一个问题
重头戏
停止Master节点及Slave节点过程中,出现停止部分Master节点交易中断60秒无法连接情况,该问题主要原因为Redis集群本身高可用切换结束之后,应用程序没有及时更新集群拓扑信息(spring-boot-starter-data-jpa框架默认关闭了集群拓扑刷新机制,默认刷新时间为60秒),从而导致交易中断 60秒的情况
步骤一:将RedisConfiguration.java添加到应用的cofing文件夹中(
import io.lettuce.core.ReadFrom;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
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.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Redis配置
*
* @author zhaoxg
*/
@Slf4j
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfiguration {
@Autowired
private RedisProperties redisProperties;
@Bean(destroyMethod = "destroy")
public LettuceConnectionFactory redisConnectionFactory() {
// redis单节点
if (null == redisProperties.getCluster() || null == redisProperties.getCluster().getNodes()) {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(redisProperties.getHost(),
redisProperties.getPort());
configuration.setPassword(redisProperties.getPassword());
return new LettuceConnectionFactory(configuration);
}
// redis集群
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 redisClusterConfiguration = new RedisClusterConfiguration();
redisClusterConfiguration.setClusterNodes(nodes);
//RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes());
redisClusterConfiguration.setPassword(redisProperties.getPassword());
redisClusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
GenericObjectPoolConfig<?> genericObjectPoolConfig = new GenericObjectPoolConfig<>();
genericObjectPoolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());
genericObjectPoolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
genericObjectPoolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
genericObjectPoolConfig.setMaxWaitMillis(redisProperties.getLettuce().getPool().getMaxWait().getSeconds());
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enableAllAdaptiveRefreshTriggers()
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(3))
.enablePeriodicRefresh(Duration.ofSeconds(6))
.build();
ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
.topologyRefreshOptions(clusterTopologyRefreshOptions).build();
LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
.poolConfig(genericObjectPoolConfig)
.clientOptions(clusterClientOptions)
.commandTimeout(redisProperties.getTimeout()).build();
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
lettuceConnectionFactory.setShareNativeConnection(false);// 是否允许多个线程操作共用同一个缓存连接,默认 true,false 时每个操作都将开辟新的连接
lettuceConnectionFactory.resetConnection();// 重置底层共享连接, 在接下来的访问时初始化
return lettuceConnectionFactory;
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
)
步骤二:修改应用pom.xml文件,添加comm-pools依赖;
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
步骤三:修改application文件
spring.redis.timeout=3000
spring.redis.cluster.max-redirects=5
spring.redis.cluster.nodes=集群信息
spring.redis.lettuce.pool.max-active=1000
spring.redis.lettuce.pool.max-idle=100
spring.redis.lettuce.pool.max-wait=5000
spring.redis.lettuce.pool.min-idle=5
spring.redis.password=密码