【技术背景】

Redis 升级配置时,出现以下2种报错:

第一种报错如下:

org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: CLUSTERDOWN The cluster is down
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:54)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:52)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
        at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
        at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:270)
        at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)
        at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:68)
        at org.springframework.data.redis.connection.DefaultedRedisConnection.get(DefaultedRedisConnection.java:260)
        at org.springframework.data.redis.core.DefaultValueOperations$1.inRedis(DefaultValueOperations.java:57)
        at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:60)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)

第二种报错如下:

org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 2 second(s)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:70)
        at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
        at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
        at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
        at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:270)
        at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)
        at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:68)
        at org.springframework.data.redis.connection.DefaultedRedisConnection.get(DefaultedRedisConnection.java:260)
        at org.springframework.data.redis.connection.DefaultStringRedisConnection.get(DefaultStringRedisConnection.java:398)
        at org.springframework.data.redis.core.DefaultValueOperations$1.inRedis(DefaultValueOperations.java:57)
        at org.springframework.data.redis.core.AbstractOperations$ValueDeserializingRedisCallback.doInRedis(AbstractOperations.java:60)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)
        at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:96)
        at org.springframework.data.redis.core.DefaultValueOperations.get(DefaultValueOperations.java:53)

【原因猜测】

  • 第一种报错是因为 Redis 在主从切换时,Redis 选举过程中出现短暂的不可用。

  • 第二种报错是因为 Redis Client 向下线的节点继续发送请求,引起的连接超时。

【解决方案】

经过查询各种资料及本地调试,最终总结出来以下的解决方案。

  • 针对第一种报错,在 Redis 集群的从节点下执行命令:cluster failover,即可把从节点提升为主节点,然后再下线从节点即可。

  • 针对第二种报错,Redis Client 需要定期刷新客户端的连接信息。对于已经下线节点及时从节点列表中剔除,不让流量继续打到下线的节点。这个需要修改客户端源码。

【操作步骤】

在这里插入图片描述

第一步:Redis 从库下线,升级配置,并重启。

第二步:在所有的从库下执行 cluster failover 命令,把从库变成主库,把主库降为从库。

第三步:等待 60s,待所有微服务节点列表都从新的主节点查询后。

第四步:把新的 Redis 从库下线,升级配置,并重启。

此时,升级完成。

【Redis Client 刷新源码】

@Bean("lettuceRedisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory() {
    ...        
    // 配置用于开启自适应刷新和定时刷新。如自适应刷新不开启,Redis集群变更时将会导致连接异常
    ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
            // 开启刷新
            .enablePeriodicRefresh(true)
            // 30s 重新拉取节点列表
            .enablePeriodicRefresh(Duration.ofSeconds(30))
            // 自适应刷新超时时间(默认30秒)
            .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))
            // 开启自适应刷新
            .enableAllAdaptiveRefreshTriggers()
            .build();
    ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
            .topologyRefreshOptions(clusterTopologyRefreshOptions) // 拓扑刷新
            .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
            .socketOptions(SocketOptions.builder().keepAlive(true).build())
            .build();
    // 连接池
    LettucePoolingClientConfiguration lettucePoolingClientConfiguration = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(readTimeout))
            .poolConfig(genericObjectPoolConfig())
            .clientOptions(clusterClientOptions)
            .build();        
    ...
}

【总结】

1、接入各种中间件之前,必须对其进行高可用的调研及压测,在宕机场景下也能尽快恢复。

2、线上操作各种中间件之前,最好能在测试环境进行预演。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部