RedisCommandTimeoutException是Lettuce客户端命令超时异常,非RuntimeException子类,需显式捕获或通过RedisSystemException统一处理;它与ConnectTimeoutException成因不同,前者可重试,后者属基础设施问题应排查网络或配置。

捕获 RedisCommandTimeoutException 而不是通用异常
Spring Boot 默认用 Lettuce,超时抛出的是 RedisCommandTimeoutException,它继承自 RedisSystemException,但**不是**RuntimeException 的子类。如果你只 catch RuntimeException 或 Exception,会漏掉它;更糟的是,某些场景下它还可能被包装成 ExecutionException(比如在 CompletableFuture 中调用异步 Redis 操作)。
正确做法是显式捕获该异常类型:
- 直接 try-catch
RedisCommandTimeoutException - 或统一处理其父类
RedisSystemException(覆盖连接失败、命令超时、序列化错误等系统级异常) - 避免只 catch
Exception——会把本该快速失败的业务异常也吞掉
区分 CommandTimeout 和 ConnectTimeout 的异常类型
这两类超时触发的异常完全不同,混用会导致兜底逻辑失效:
-
RedisCommandTimeoutException:对应spring.redis.timeout(即 Lettuce 的commandTimeout),发生在连接已建立、命令发出去但没收到响应时 -
io.netty.channel.ConnectTimeoutException:由 Netty 底层抛出,对应 Lettuce 的connectTimeout(YAML 里无配置项,必须 Java 代码设置),发生在 TCP 握手阶段失败 - 前者可被
RetryTemplate重试;后者重试前需确认网络/地址/DNS 是否恢复,否则只是反复失败
用 RetryTemplate 封装操作,而非手动 sleep+retry
手动写重试容易出错:比如在事务内重试会破坏一致性,或在异步流中丢失上下文。Spring Retry 提供了可控的重试能力:
- 配置
SimpleRetryPolicy(maxAttempts = 3)+FixedBackOffPolicy(backOffPeriod = 1000) - 限定重试的异常类型:
retryPolicy.setRetryExceptions(RedisCommandTimeoutException.class) - 务必排除
ConnectTimeoutException——它代表基础设施问题,不该盲目重试 - 注意:重试不会自动续传命令,Lettuce 的命令是幂等的(如 GET、HGET),但非幂等操作(如 INCR、LPUSH)需自行保证语义安全
异常日志要带 Redis 命令上下文
只记录 “Read timed out” 毫无调试价值。Lettuce 不自带命令信息,需手动增强:
- 在调用
redisTemplate.opsForValue().get(key)前,用 MDC 记录 key 和操作类型:MDC.put("redis_cmd", "GET " + key) - 捕获异常后,在日志中打印完整命令、耗时、key、client IP(可通过
LettuceConnectionFactory.getClusterClientOptions()获取部分连接信息) - 避免在高 QPS 场景下对每个命令都打全量日志,可用采样策略(如仅超时 > 3s 的才记录)
最常被忽略的一点:RedisCommandTimeoutException 往往是结果,不是原因。它背后可能是大 Key、慢查询、连接池耗尽,或 Redis 主从同步延迟。捕获异常只是止损,定位根因仍需结合 slowlog get、info clients 和连接池监控指标。不查服务端状态就加 retry,等于给堵车路口多派几辆车。


















