Redis命令写入超时异常分析与解决

小豆丁 14天前 ⋅ 14 阅读
聊天记录
头像

【MQ消费异常】 runtimeId:order-center@app-8 Virtual host:turnright 队列:NOTIFICATION.V1.OrderDynamicInfoNotification#handleOrderDynamicInfoNotice 日志关键字:5de674a288774f6ea4d0e63ff5ab3154 异常类型:org.springframework.dao.QueryTimeoutException 异常信息:Command still hasn't been written into connection! Check CPU usage of the JVM. Check that there are no blocking invocations in async/reactive/rx listeners or subscribeOnElements method. Check connection with Redis node: 172.26.159.156/172.26.159.156:6379 for TCP packet drops. Try to increase nettyThreads setting. Node source: NodeSource [slot=11160, addr=null, redisClient=null, redirect=null, entry=null], connection: RedisConnection@2136349395 [redisClient=[addr=redis://172.26.159.156:6379], channel=[id: 0x880d1447, L:/172.17.0.2:58390 - R:172.26.159.156/172.26.159.156:6379], currentCommand=null, usage=1], command: (SET), params: [[106, 111, 98, 45, 108, 111, 99, 107, 58, 113, ...], [65, 68, 68, 69, 68, 58, 50, 48, 50, 53, ...], PX, 5000, NX] after 3 retry attempts; nested exception is org.redisson.client.RedisTimeoutException: Command still hasn't been written into connection! Check CPU usage of the JVM. Check that there are no blocking invocations in async/reactive/rx listeners or subscribeOnElements method. Check connection with Redis node: 172.26.159.156/172.26.159.156:6379 for TCP packet drops. Try to increase nettyThreads setting. Node source: NodeSource [slot=11160, addr=null, redisClient=null, redirect=null, entry=null], connection: RedisConnection@2136349395 [redisClient=[addr=redis://172.26.159.156:6379], channel=[id: 0x880d1447, L:/172.17.0.2:58390 - R:172.26.159.156/172.26.159.156:6379], currentCommand=null, usage=1], command: (SET), params: [[106, 111, 98, 45, 108, 111, 99, 107, 58, 113, ...], [65, 68, 68, 69, 68, 58, 50, 48, 50, 53, ...], PX, 5000, NX] after 3 retry attempts 是什么意思?

头像

这个错误信息表明你的应用程序(order-center@app-8)在使用 Redisson 客户端库操作 Redis 时发生了超时异常。核心问题是:尝试执行一个 SET 命令(用于设置分布式锁)时,该命令甚至无法成功写入到与 Redis 服务器的网络连接中。

让我们分解一下关键信息:

  1. 异常类型:

    • org.springframework.dao.QueryTimeoutException: Spring 框架抛出的超时异常,通常由底层数据访问操作(这里是 Redis 操作)超时引起。
    • org.redisson.client.RedisTimeoutException: 这是根本原因,来自 Redisson 客户端库,表明对 Redis 的操作超时了。
  2. 核心错误信息:

    • Command still hasn't been written into connection!: 这是最关键的一句。 它意味着 Redisson 客户端尝试发送一个 Redis 命令(这里是 SET)到服务器,但这个命令在尝试写入底层的网络连接(Netty Channel)时就失败了,或者卡住了,以至于在超时时间内都没能成功写入。这通常发生在网络连接或客户端线程出现严重问题时。
  3. Redisson 建议的排查方向:

    • Check CPU usage of the JVM.: 检查你的应用程序(order-center)所在 JVM 的 CPU 使用率是否过高。CPU 满载是导致 Netty 事件循环线程无法及时处理网络 IO 的最常见原因。
    • Check that there are no blocking invocations in async/reactive/rx listeners or subscribeOnElements method.: 检查你的代码中,尤其是在异步、响应式或 RxJava 的回调函数(listeners)或 subscribeOnElements 方法中,是否存在阻塞操作(如耗时的同步 IO、复杂的计算、同步锁等待等)。这些阻塞操作会卡住 Netty 的 IO 线程,导致其他网络操作(包括发送 Redis 命令)无法进行。
    • Check connection with Redis node: 172.26.159.156/172.26.159.156:6379 for TCP packet drops.: 检查应用程序服务器与 Redis 服务器 172.26.159.156:6379 之间的网络连接是否存在问题,特别是 TCP 丢包。网络不稳定或防火墙/安全组策略可能导致连接中断或数据包丢失。
    • Try to increase nettyThreads setting.: 尝试增加 Redisson 配置中的 nettyThreads 参数值。这个参数控制 Redisson 用于处理网络 IO 的 Netty 线程数量。如果默认值太小,在高并发下可能成为瓶颈。
  4. 操作细节:

    • command: (SET), params: [[106, 111, 98, 45, 108, 111, 99, 107, 58, 113, ...], [65, 68, 68, 69, 68, 58, 50, 48, 50, 53, ...], PX, 5000, NX]
      • 这是尝试执行的 Redis 命令:SET key value PX 5000 NX
      • key: 字节数组 [106, 111, 98, 45, 108, 111, 99, 107, 58, 113, ...] 对应字符串 job-lock:q... (一个分布式锁的 key)。
      • value: 字节数组 [65, 68, 68, 69, 68, 58, 50, 48, 50, 53, ...] 对应字符串 ADDED:2025...
      • PX 5000: 设置 key 的过期时间为 5000 毫秒(5秒)。
      • NX: 仅当 key 不存在时才设置(实现锁的互斥性)。
    • after 3 retry attempts: Redisson 内部重试了 3 次都失败了。
    • Node source: ... connection: RedisConnection@2136349395 [redisClient=[addr=redis://172.26.159.156:6379]: 操作的目标是 Redis 节点 172.26.159.156:6379

总结与含义:

你的应用程序(order-center@app-8)在处理消息队列 NOTIFICATION.V1.OrderDynamicInfoNotification#handleOrderDynamicInfoNotice 中的消息时,需要获取一个名为 job-lock:q... 的分布式锁(使用 Redis 的 SET ... NX PX 命令实现)。然而,在执行这个关键的锁获取操作时,命令甚至无法通过网络连接发送出去,导致操作超时失败(RedisTimeoutException),进而触发了 Spring 的 QueryTimeoutException,最终导致 MQ 消息消费异常。

最可能的原因(按优先级):

  1. JVM CPU 使用率过高: 这是 Redisson 首要怀疑的原因。如果应用服务器的 CPU 被耗尽(接近 100%),负责网络 IO 的 Netty 线程可能无法获得足够的 CPU 时间片来及时处理发送 Redis 命令的任务。
  2. 代码中存在阻塞 Netty IO 线程的操作: 在异步回调、响应式流处理(如 Reactor/RxJava)的某些环节(尤其是 subscribeOnElements 或类似监听器方法中),如果执行了耗时的阻塞操作(如同步数据库查询、文件读写、复杂计算、Thread.sleep、等待锁等),会阻塞处理网络事件的 Netty IO 线程。一个被阻塞的 IO 线程就无法处理发送 Redis 命令的请求。
  3. 网络问题: 应用程序服务器与 Redis 服务器 172.26.159.156:6379 之间的网络连接不稳定,存在丢包、高延迟或瞬时中断。
  4. Netty 线程池配置不足: 默认的 nettyThreads 配置可能不足以应对当前的并发负载。

下一步排查建议:

  1. 立即检查应用服务器 CPU: 查看 order-center@app-8 所在服务器的 CPU 使用率历史记录(如 Grafana, Zabbix, top/htop)。如果持续或频繁接近 100%,这就是首要嫌疑。
  2. 检查 GC 日志: 分析 JVM 垃圾回收日志,看是否有长时间的 Full GC 停顿。长时间的 GC 也会导致所有线程(包括 Netty IO 线程)暂停。
  3. 检查线程堆栈: 在问题发生时或发生后,尽快获取 order-center@app-8 的线程堆栈转储(jstack 或通过 APM 工具)。重点分析名为 redisson-netty-* 的线程的状态。 如果这些线程大部分处于 RUNNABLE 状态执行业务代码(而不是网络 IO),或者卡在某个锁(WAITING, BLOCKED)上,就说明有阻塞操作侵占了 Netty IO 线程。
  4. 检查网络:
    • 在应用服务器上使用 pingtraceroute/mtr172.26.159.156,检查延迟和丢包。
    • 使用 redis-cli -h 172.26.159.156 -p 6379 --latency 测试 Redis 命令的延迟。
    • 检查服务器之间防火墙、安全组规则是否允许稳定通信。
    • 查看 Redis 服务器本身的网络和负载情况。
  5. 审查代码: 仔细检查消息监听器 handleOrderDynamicInfoNotice 方法及其调用的所有逻辑,特别关注在 Redisson 的异步方法回调、Reactor/RxJava 的 subscribeOn/publishOn(尤其是使用不恰当的调度器时)、doOnNextdoOnSuccess 等操作符内部,是否存在任何可能导致线程阻塞的操作。确保耗时或阻塞操作被正确地卸载到专用的线程池(如使用 Schedulers.boundedElastic())。
  6. 调整配置(谨慎): 如果确认 CPU 和网络没问题,且代码审查没有发现明显阻塞点,但并发量确实很高,可以考虑在 Redisson 配置中适当增加 nettyThreads 的数量(例如从默认的 当前CPU核心数 * 2 增加到 当前CPU核心数 * 3当前CPU核心数 * 4)。但这通常是治标,找到根本原因(CPU 或阻塞代码)更重要。

总之,这个错误强烈指向应用服务器端(JVM CPU 或代码阻塞 Netty 线程)或网络连接问题,而不是 Redis 服务端本身的问题(虽然也需要确认其健康状态)。优先排查应用服务器的 CPU 使用率和线程堆栈。

全部评论: 0

    我有话说: