tencent cloud

腾讯云分布式缓存数据库(兼容 Redis)

动态与公告
产品动态
公告
新手指引
产品简介
产品概述
产品优势
应用场景
存储引擎
产品系列
产品版本
规格与性能
读写分离
多可用区部署
地域和可用区
名词解释
购买指南
计费概述
定价中心
购买实例
续费说明(包年包月)
退费说明(包年包月)
欠费说明
按量转包年包月
快速入门
快速创建实例
连接 Redis 实例
操作指南
操作总览
连接数据库实例
管理实例
升级实例
管理节点(Redis/ValKey 版)
管理多可用区
备份与恢复
账号管理
参数配置
慢查询
访问管理
网络与安全
监控与告警
事件管理(Redis/ValKey 版)
数据迁移
Redis 版全球复制
数据库审计
诊断优化
Sentinel 模式
开发准则
命名规则
基本使用准则
Key 与 Value 设计原则
命令使用准则
客户端程序设计准则
连接池配置
命令参考
命令参考概览
Redis 版与 Valkey 版命令兼容性
大版本命令使用差异
Proxy 架构与直连模式的使用差异
命令更多操作(Redis/Valkey 版)
Memcached 版命令兼容性
实践教程
基于 Spring Boot 搭建 Redis 客户端监控
Redis 客户端连接配置策略与实践
集群架构全局 SCAN 使用指南
实例安全下线
热 Key 与 大 key
可用区迁移方案
故障处理
连接异常
Redisson 客户端超时重连异常分析及解决方案
性能排查与调优
API 文档
History
Introduction
API Category
Making API Requests
Instance APIs
Parameter Management APIs
Other APIs
Backup and Restoration APIs
Region APIs
Monitoring and Management APIs
Log APIs
Data Types
Error Codes
常见问题
使用常见问题
连接登录问题
购买相关问题
相关协议
服务等级协议
Terms of Service
词汇表
联系我们
文档腾讯云分布式缓存数据库(兼容 Redis)故障处理Redisson 客户端超时重连异常分析及解决方案

Redisson 客户端超时重连异常分析及解决方案

PDF
聚焦模式
字号
最后更新时间: 2026-03-17 18:23:49
本文旨在简单探讨 Redisson 客户端在面临网络波动及服务故障切换时所遇到的业务阻塞问题,并提出一种简洁而有效的业务层面解决方案,为类似问题的解决提供启发。

问题背景

通过 Redisson 客户端连接腾讯云分布式缓存数据库实例时,VPC 网关发生故障,持续时间接近 10 秒。为了恢复网络连接,隔离故障网络设备并重新恢复网络链路。尽管连接数得以恢复到正常的业务水平,但受网络故障影响的业务仍然持续性报错。只有重启客户端之后,系统重新建立了与 Redis 数据库的连接,业务请求才得以恢复正常。

异常分析

根据异常产生过程,对问题场景进行复现,获取报错日志信息,深度分析 Redisson 连接的内部工作机制,定位潜在问题。

复现问题

面对网络持续性抖动这一复杂场景,为了便于测试和直观展示,通过在云服务器(CVM)上配置 iptables 规则,阻断对 Redis 服务的访问,从而模拟网络抖动的效果,再解除阻断,恢复网络链路,收集业务请求时的报错日志信息。
1. 为了确保代码的清晰性和易于理解,设计一个简洁的 Redisson 客户端示例,用于连接到腾讯云分布式缓存数据库服务器,并在连接成功后执行一个简单的 while 循环,以模拟一个持续的任务。
说明:
如下代码,config-file.yaml配置文件,设置 Redis 服务器的地址、端口、密码,以及 timeout (3000ms)等信息,根据配置信息,建立 Redis 的连接。
本示例以 Redisson 3.23.4 版本为例介绍,不同版本的 Redisson 可能存在一些差异,建议查阅或咨询 Redisson 官方文档。
package org.example;

// 导入Redisson相关的包
import org.redisson.Redisson;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.connection.balancer.RoundRobinLoadBalancer;

// 导入Java IO相关的包
import java.io.File;
import java.io.IOException;

public class Main {
public static void main(String[] args) throws IOException {
// 从YAML文件中读取Redisson的配置信息
Config config = Config.fromYAML(new File("/data/home/test**/redissontest/src/main/resources/config-file.yaml"));
RedissonClient redisson = Redisson.create(config);
// 初始化一个计数器变量
int i = 0;
// 循环1000000次
while(i++ < 1000000){
// 获取一个原子长整型对象,键值为当前计数器的值
RAtomicLong atomicLong = redisson.getAtomicLong(Integer.toString(i));
// 对原子长整型对象的值进行原子减1操作
atomicLong.getAndDecrement();
}
// 关闭Redisson客户端
redisson.shutdown();
}
}

2. 客户端正常启动并运行一小段时间后,修改 iptables 配置,阻断 Redis 的连接。
sudo iptables -A INPUT -s 10.0.16.6 -p tcp --sport 6379 -m conntrack --ctstate NEW,ESTABLISHED -j DROP
3. 腾讯云分布式缓存数据库控制台系统监控页面,查看连接数量的指标。该指标呈直线式下降趋势,如下图所示。

4. 执行如下命令,修改 iptables 配置,解除连接阻断,恢复网络。
sudo iptables -D INPUT -s 10.0.16.6 -p tcp --sport 6379 -m conntrack --ctstate NEW,ESTABLISHED -j DROP
5. 在网络恢复正常之后,客户端迅速进行重连。在 腾讯云分布式缓存数据库控制台系统监控页面再次查看指标。连接数量恢复到原始水平,但总请求依然异常,如下所示。

6. 获取异常日志信息,如下图所示。


分析源码

根据报错信息,逐一分析代码,定位问题,可了解 Redisson 在重连出现异常报错之后,并没有尝试重新获取新的 Channel 的行为,导致请求持续报错。
1. 报错信息Unable to send PING command over channel,查看 PingConnectionHandler 代码,如下所示。可以看出,PING 重连探测失败报错后,执行ctx.channel().close(),关闭了异常的 channel,并且执行connection.getRedisClient().getConfig().getFailedNodeDetector().onPingFailed();检测当前连接客户端的状态。
if(connection.getUsage() == 0 && future != null && (future.cancel(false) || cause(future) != null)) {
Throwable cause = cause(future);
if (!(cause instanceof RedisRetryException)) {
if (!future.isCancelled()) {
log.error("Unable to send PING command over channel: {}", ctx.channel(), cause);
}
log.debug("channel: {} closed due to PING response timeout set in {} ms", ctx.channel(), config.getPingConnectionInterval());
ctx.channel().close();
connection.getRedisClient().getConfig().getFailedNodeDetector().onPingFailed();
} else {
connection.getRedisClient().getConfig().getFailedNodeDetector().onPingSuccessful();
sendPing(ctx);
}
} else {
connection.getRedisClient().getConfig().getFailedNodeDetector().onPingSuccessful();
sendPing(ctx);
}
2. 进一步分析 Connection 来源RedisConnection connection = RedisConnection.getFrom(ctx.channel());查看创建连接的构造函数 RedisConnection(),如下所示。该函数创建更新 channel 属性和记录最后使用连接的时间,但并没有切换新 Channel 尝试连接的行为。
public <C> RedisConnection(RedisClient redisClient, Channel channel, CompletableFuture<C> connectionPromise) {
this.redisClient = redisClient;
this.connectionPromise = connectionPromise;

updateChannel(channel);
lastUsageTime = System.nanoTime();

LOG.debug("Connection created {}", redisClient);
}
// updateChannel 更新 Channel的属性
public void updateChannel(Channel channel) {
if (channel == null) {
throw new NullPointerException();
}
this.channel = channel;
channel.attr(CONNECTION).set(this);
}
3. 若更新集群信息,连接会出现如下错误信息。
ERROR org.redisson.cluster.ClusterConnectionManager - Can't update cluster stateorg.redisson.client.RedisTimeoutException: Command execution timeout for command: (CLUSTER NODES), params: [], Redis client: [addr=redis://10.0.16.7:6379] at org.redisson.client.RedisConnection.lambda$async$0(RedisConnection.java:256) at io.netty.util.HashedWheelTimer$HashedWheelTimeout.run(HashedWheelTimer.java:715) at io.netty.util.concurrent.ImmediateExecutor.execute(ImmediateExecutor.java:34) at io.netty.util.HashedWheelTimer$HashedWheelTimeout.expire(HashedWheelTimer.java:703) at io.netty.util.HashedWheelTimer$HashedWheelBucket.expireTimeouts(HashedWheelTimer.java:790) at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:503) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:829)
4. ClusterConnectionManager.java 中可以定位到更新集群的报错信息片段。可以看出,在抛出Can't update cluster state异常后,仅检查了集群状态的变更,而并没有从连接池中拿取新连接进行操作的行为,直接返回。
private void checkClusterState(ClusterServersConfig cfg, Iterator<RedisURI> iterator, AtomicReference<Throwable> lastException) {
if (!iterator.hasNext()) {
if (lastException.get() != null) {
log.error("Can't update cluster state", lastException.get());
}
// 检查集群状态的变更
scheduleClusterChangeCheck(cfg);
return;
}
......
}

解决方案

基于上述问题,对业务侧代码进行调整,在出现异常连接时,主动打断当前的连接线程,重新进行 Channel 和连接配置,并进行重连操作。

调整代码

如下代码,以问题复现时设计的代码为基础进行优化,补充异常重连的操作。
package org.example;
import org.redisson.Redisson;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.connection.balancer.RoundRobinLoadBalancer;
import java.io.File;
import java.io.IOException;

public class Main {
public static void main(String[] args) throws IOException {
boolean connected = false;
RedissonClient redisson = null;
while (!connected) {
try {
Config config = Config.fromYAML(new File("/data/home/sharmaxia/redissontest/src/main/resources/config-file.yaml"));
redisson = Redisson.create(config);
connected = true;
} catch (Exception e) {
e.printStackTrace();
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt(); //主动打断当前thread重新从连接池中拿连接
}
}
}
int i = 0;
while (i++ < 1000000) {
try {
RAtomicLong atomicLong = redisson.getAtomicLong(Integer.toString(i));
atomicLong.getAndDecrement();
} catch (Exception e) {
e.printStackTrace();
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt(); //主动打断当前thread重新从连接池中拿连接
}
i--;
}
}
redisson.shutdown();
}
}

验证结果

查看连接数量与总请求的监控指标,确认业务恢复正常,以验证代码经过调整后,解决 Redisson 客户端超时重连异常造成的业务故障。

连接数量



总请求



帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈