[springboot, lettuce] io.lettuce.core.RedisCommandTimeoutException: Command timed out after

本文分析了SpringBoot应用中遇到的Redis命令超时问题,包括RedisCommandTimeoutException的诊断和可能的原因,如服务器故障、命令执行时间过长、配置不匹配、EventLoop阻塞等。提出了相应的解决方案,如调整SpringBoot的Redis配置、修改Redis服务器的tcp-keepalive参数、切换到Jedis客户端、添加心跳检查和优化服务器性能。同时,也提醒注意Redis的阻塞问题,如大对象操作、CPU竞争和网络延迟。

     环境上用的springboot 2.3.1, 项目上线已经两年,今天第一次遇到这个lettuce的Redis “Command timed out”,于是网上查了查资料,找一下原因和解决方法。

GitHub ISSUE:

https://github.com/lettuce-io/lettuce-core/issues?q=is%3Aissue+Command+timed+out+after

https://github.com/lettuce-io/lettuce-core/issues/1362

参考官方连接:

Lettuce Reference Guide

RedisCommandTimeoutException with a stack trace like:

io.lettuce.core.RedisCommandTimeoutException: Command timed out after 1 minute(s)
at io.lettuce.core.ExceptionFactory.createTimeoutException(ExceptionFactory.java:51)
at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:114)
at io.lettuce.core.FutureSyncInvocationHandler.handleInvocation(FutureSyncInvocationHandler.java:69)
at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80)
at com.sun.proxy.$Proxy94.set(Unknown Source)

Diagnosis:

  1. Check the debug log (log level DEBUG or TRACE for the logger io.lettuce.core.protocol)

  2. Take a Thread dump to investigate Thread activity

Cause:

Command timeouts are caused by the fact that a command was not completed within the configured timeout. Timeouts may be caused for various reasons:

  1. Redis server has crashed/network partition happened and your Redis service didn’t recover within the configured timeout

  2. Command was not finished in time. This can happen if your Redis server is overloaded or if the connection is blocked by a command (e.g. BLPOP 0, long-running Lua script). See also blpop(Duration.ZERO, …) gives RedisCommandTimeoutException.

  3. Configured timeout does not match Redis’s performance.

  4. If you block the EventLoop (e.g. calling blocking methods in a RedisFuture callback or in a Reactive pipeline). That can easily happen when calling Redis commands in a Pub/Sub listener or a RedisConnectionStateListener.

Action:

      Check for the causes above. If the configured timeout does not match your Redis latency characteristics, consider increasing the timeout. Never block the EventLoop from your code.

 列举一下处理方式

1、修改spring.redis.timeout和cluster.refresh(大部分情况都能解决)

      环境:spring-boot-starter 2.x 和 sprig-data-starter-data-redis 2.x

在使用

connection.bRPop(timeout, rawKey);

方法时,如果这里的timeout大于springboot配置文件的spring.redis.timeout,就会出现异常io.lettuce.core.RedisCommandTimeoutException: Command timed out after。

所以解决方法就是timeout不要超出连接池的timeout就好了.

当然这个是第一步,可以进行观察是否有作用

#Spring Boot 从 2.0版本开始,将默认的Redis客户端Jedis替换为Lettuce
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
spring.redis.timeout=50000ms

其实springboot 2.3.0 支持 自动刷新,增加下面配置,可以进行redis连接刷新

# 开启cluster自适应刷新 周期600秒
spring.redis.lettuce.cluster.refresh.adaptive=true
spring.redis.lettuce.cluster.refresh.period=600000

可以适当的修改这两个值,进行测试和观察验证。

2、修改redis.conf中的tcp-keepalive

      将spring boot redis配置设置超时时间比redis.conf中的timeout要小,比如spring boot redis中设置超时30秒,那么服务器中redis timeout设置为40秒;另外将redis.conf中的tcp-keepalive改成10:

# Unix socket.
#
# Specify the path for the Unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocketperm 700
 
# Close the connection after a client is idle for N seconds (0 to disable)
timeout 40
 
# TCP keepalive.
#
# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
# of communication. This is useful for two reasons:
#
# 1) Detect dead peers.
# 2) Take the connection alive from the point of view of network
#    equipment in the middle.
#
# On Linux, the specified value (in seconds) is the period used to send ACKs.
# Note that to close the connection the double of the time is needed.
# On other kernels the period depends on the kernel configuration.
#
# A reasonable value for this option is 300 seconds, which is the new
# Redis default starting with Redis 3.2.1.
tcp-keepalive 10

       将tcp-keepalive改成1-50左右的数字,比如改成10,之前是0或者300貌似,改成小一点的数字就行了,原理百度或者根据配置文件的注释英文翻译一下,记得改完重启Redis.       

      springboot版本是2.1,Redis是5,貌似Redis3.2.1开始默认60,所以建议改成1-20之间就不那么卡或者报错.

       生产来说感觉这个方法还是有缺陷,比如tx云redis禁止修改tcp-keepalive,我觉得归根结底还是应该考虑客户端的重连检测机制,多应用的情况下心跳都消耗不少带宽了。

3、lettuce换成jredis

      lettuce换成jredis,因为通过用Wireshark发现jredis默认是发心跳包的,而lettuce是不发心跳包的,所以能保持连接状态,更改如下

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </exclusion>
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!-- jedis客户端 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2,使用jedis必须依赖它-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

4、添加心跳

自己写个定时器轮询访问redis,模拟一下心跳,其实这个方法也没什么不好,只是需要自己写代码:

@Scheduled(cron = "0/10 * * * * *")
public void timer() {
    redisTemplate.opsForValue().get("heartbeat");
}

5、提高服务器硬件性能

        不排除可能有服务器硬件原因

6、Redis阻塞问题

        精简数据量,优化redis的key和value大小,较少时间消耗

  1. Redis数据结构或API使用不合理,导致可能存在大对象且大对象使用复杂度高的命令;

    • 对一个有千万个元素的hash执行hgetall操作, 或del操作.类似的这种操作都会造成Redis阻塞
    • 对于这种大对象可以采用redis-cli -h {host} -p {port} bigkeys 来查看。但是该命令只能查询某类型中的其
      中最大的一个key。如果你想查询多个。可以采用修改redis-cli源代码的方式(Redis的源代码是C)。如果不想修 改源代码的话也可以使用scan来完成。
    • 对于Scan命令需要注意。该命令只能扫描单台Redis上的数据。如果你是一个集群,需要每台机器执行一遍。但是如果你使用开源的客户端的话(比如:Java的Lettuce客户端)就已经帮你把scan命令实现为可以扫描整个集群了。
    • 然后对大对象进行拆分。具体拆分要视业务而定了。
  2. Redis的cpu使用率接近100%

    • 从机同步主机数据。从机接受到rdb文件后从磁盘加载数据

    • 主从持久化数据。

    • 将cpu使用率达到100%,有可能是真实业务访问量确实很大。单台Redis达到每秒处理6万+的请求。这个时候就只能做水平扩展了

    • 如果Redis每秒操作数只有几百,或者几千,且cpu还是很高的话就有可能使用了高算法复杂度的命令。例如hgetall。还有一种可能是内存的过度优化导致。这种情况目前暂时没有遇到,但也纳入考虑范围。

  3. CPU竞争

    • Redis是一个CPU密集型的应用,不适合和其他CPU密集的服务部署在一起。

    • 在生产环境中,我们一台服务器的配置是32核逻辑cpu, 256GB内存。每台机器如果只部署一台Redis比较浪费。所以可能会一台机器部署多个Redis。通常会将Redis进程绑定到CPU上。但是在生成RDB文件或者AOF持久话时,就会产生子进程。这样子进程与父进程会产生CPU竞争。所以当开启持久化或者主节点。不建议绑定CPU

  4. 内存交换

    • Redis是一个内存型数据库,所有数据全部放在内存中。所以强烈建议不开启内存交换
  5. 网络问题

    • 主从同步网络延迟较大的话,导致从机经常断线重连。如果断线时间久了。导致从机再次连接上主机时会全量同步,这时主机,从机都会收到影响

参考:

redis io.lettuce.core.RedisCommandTimeoutException: Command timed out after-CSDN博客

解决springboot2整合Redis 后某个接口报错 java.io.IOException: 远程主机强迫关闭了一个现有的连接_java.io.ioexception: 远程主机强迫关闭了一个现有的连接。-CSDN博客

解决io.lettuce.core.RedisCommandTimeoutException: Command timed out, mysql 访问15分钟阻塞等待_caused by: io.lettuce.core.rediscommandtimeoutexce-CSDN博客

https://ppj19891020.github.io/2020/09/04/20200904Lettuce%E8%B6%85%E6%97%B6%E9%97%AE%E9%A2%98/

### 解决 RedisCommandTimeoutException 命令超时问题 当遇到 `io.lettuce.core.RedisCommandTimeoutException` 异常时,通常意味着客户端发送给 Redis 的命令未能在指定时间内得到响应。这可能是由于网络延迟、服务器负载过高或其他配置不当引起的。 #### 配置连接池参数 调整 Lettuce 连接池的相关参数可以有效缓解此问题。通过修改最大重试次数和等待时间来增强系统的健壮性: ```properties spring.redis.timeout=6000 # 设置总的读写超时时间为6秒 spring.redis.lettuce.pool.max-active=8 # 最大活跃连接数设为8 spring.redis.lettuce.pool.max-idle=8 # 最大空闲连接数同样设置成8 spring.redis.lettuce.pool.min-idle=0 # 初始创建最小空闲实例数量为0 ``` 这些属性可以在 Spring Boot 应用程序的 `application.properties` 文件中定义[^1]。 #### 修改默认超时设置 对于特定场景下的需求,还可以进一步自定义超时策略。例如,在初始化 RedisTemplate 或者 ReactiveRedisTemplate 实例之前,可以通过编程方式设定更详细的超时选项: ```java @Configuration public class RedisConfig { @Bean public LettuceConnectionFactory redisConnectionFactory() { LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() .commandTimeout(Duration.ofSeconds(10)) // 将命令执行的最大允许时间延长至10秒 .build(); return new LettuceConnectionFactory(RedisStandaloneConfiguration(), clientConfig); } } ``` 上述代码片段展示了如何利用 Builder 模式构建更加灵活可控的客户端配置对象,并将其应用于连接工厂之中[^2]。 #### 排查潜在原因 除了优化客户端侧的配置外,还应考虑以下几个方面以彻底解决问题: - **检查服务端状态**:确认目标 Redis 实例运行状况良好,无明显性能瓶颈; - **验证防火墙规则**:确保应用程序所在主机能够无障碍访问 Redis 所监听的 IP 地址及端口; - **评估业务逻辑合理性**:排查是否存在长时间占用资源的操作导致阻塞现象发生; 最后建议定期监控应用日志以及 Redis 性能指标,以便及时发现并处理可能出现的新情况[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值