springboot集成redis集群实现集群拓扑动态刷新

一个redis-cluster的三主三从集群,在其中一个master节点挂了之后,springboot集成redis集群配置信息没有及时刷新,出现读取操作报错。下面聊聊如何实现springboot集成redis集群实现集群拓扑动态刷新。

开启redis client的集群拓扑刷新功能,不同的方式,采用不同的处理方式:

  • jedis client方式;
  • luttuce client方式;
  • springboot1.x之前方式;
  • springboot2.0~2.3方式;
  • springboot2.3之后方式;

1、jedis方式

jedis client默认自动支持集群拓扑刷新(由于jedis通过自身异常反馈来识别重连、刷新服务端的集群信息机制,保证其自动故障恢复)

2、lettuce方式

lettuce client默认未开启集群拓扑刷新,需要手动开启动态刷新。

ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
            .autoReconnect(true)
            .maxRedirects(6)
            .topologyRefreshOptions(ClusterTopologyRefreshOptions.builder()
                    .enablePeriodicRefresh(30000, TimeUnit.MILLISECONDS)
                    .enableAllAdaptiveRefreshTriggers()
                    .build())
            .build();
RedisClusterClient redisClusterClient = RedisClusterClient.create(clientResources, redisURIs);
redisClusterClient.setOptions(clusterClientOptions);

3、springboot1.x版本环境

springboot1.x之前版本默认使用jedis,无需手动开启动态刷新。

4、springboot2.0~2.3版本环境

springboot2.0-2.3版本默认使用lettuce,默认不支持属性配置集群拓扑刷新。
解决方案:

  • 使用jedis,不需要手动指定开启刷新;
    依赖如下:

    org.springframework.boot
    spring-boot-starter-data-redis
    
        
            io.lettuce
            lettuce-core
        
    



    redis.clients
    jedis

配置文件类似如下:

spring.redis.cluster.nodes=192.168.100.1:6379,192.168.100.2:6379,192.168.100.3:6379,192.168.100.4:6379,192.168.100.5:6379,192.168.100.6:6379
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1s
spring.redis.jedis.pool.min-idle=0

  • 使用lettuce,需要增加配置类,需要手动开启刷新;

配置类如下:

@Configuration
public class RedisConfig {

    @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集群
        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()
            // 开启自适应刷新
            .enableAdaptiveRefreshTrigger()
            // 开启定时刷新
            .enablePeriodicRefresh(Duration.ofSeconds(5))
            .build();

        ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
            .topologyRefreshOptions(clusterTopologyRefreshOptions).build();

        LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
            .poolConfig(genericObjectPoolConfig)
//            .readFrom(ReadFrom.SLAVE_PREFERRED)  //读写分离:主写从读模式配置
            .clientOptions(clusterClientOptions).build();

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
        lettuceConnectionFactory.setShareNativeConnection(false);// 是否允许多个线程操作共用同一个缓存连接,默认 true,false 时每个操作都将开辟新的连接
        lettuceConnectionFactory.resetConnection();// 重置底层共享连接, 在接下来的访问时初始化
        return lettuceConnectionFactory;
    }

    /**
     * RedisTemplate配置
     */
    @Bean
    public RedisTemplate redisTemplate(LettuceConnectionFactory factory) {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // 使用注解@Bean返回RedisTemplate的时候,同时配置hashkey和hashValue的序列虎方式
        // key采用String的序列化方式
        redisTemplate.setKeySerializer(keySerializer());
        // value使用jackson序列化方式
        redisTemplate.setValueSerializer(valueSerializer());

        // hash的key采用String的序列化方式
        redisTemplate.setHashKeySerializer(keySerializer());
        // hash的value使用jackson序列化方式
        redisTemplate.setHashValueSerializer(valueSerializer());

        /**必须执行这个函数,初始化RedisTemplate*/
        // 需要先调用afterPropertiesSet方法,此方法是应该是初始化参数和初始化工作。
        redisTemplate.afterPropertiesSet();
        log.info("序列化完成!");
        return redisTemplate;
    }

    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * key键序列化方式
     *
     * @return RedisSerializer
     */
    private RedisSerializer keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * value值序列化方式
     *
     * @return
     */
    private Jackson2JsonRedisSerializer valueSerializer() {
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }
}

配置文件类似如下:

#Redis Configuration
spring.redis.cluster.max-redirects=10
spring.redis.cluster.nodes=192.168.100.1:6379,192.168.100.2:6379,192.168.100.3:6379,192.168.100.4:6379,192.168.100.5:6379,192.168.100.6:6379
spring.redis.timeout=60000ms
spring.redis.password=
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=-1ms

注意:注入LettuceConnectionFactory后,一定要记得注入RedisTemplate,并 redisTemplate.setConnectionFactory(redisConnectionFactory);

5、springboot2.3之后版本环境

springboot2.3之后版本默认使用lettuce,默认支持属性配置开启集群拓扑刷新,其解决方案:属性配置开启即可。
配置文件如下:

spring.redis.lettuce.cluster.refresh.adaptive= true
spring.redis.lettuce.cluster.refresh.period=30000

spring:
  redis:
	lettuce:
	  cluster:
		refresh:
		  adaptive: true
		  period: 30000    # 30秒自动刷新一次

6、小结

在springboot工程中,集成了redis集群,当集群中主节点挂掉或从节点挂掉,springboot工程调用redis进行读取操作出现报错,主要排查两方面:第一排查redis集群部署配置的是否正常,当主节点挂掉后redis集群会自动进行故障转移,形成新的3主节点,其中一个主节点无从节点,可以登录客户端通过命令cluster nodes查看;第二排查springboot工程中集成redis集群使用的是那种方式,具体解决方式见上面。但工程中引入了shiro安全框架,则其session共享redis集群方案可能还是会有问题,具体深入分析解决。
感兴趣的小伙伴可以尝试~

你可能感兴趣的:(redis,springboot,spring,boot,redis集群拓扑动态刷新,redis集群,redis集群高可用)