Redis 连接池耗尽的一次异常定位
笔记哥 /
05-10 /
33点赞 /
0评论 /
195阅读
转载请注明出处:
最近在项目中遇到一个奇怪的现象,项目运行环境中的redis在业务运行中,一直没有更新redis的值,在服务的日志中也没有看到相关的异常,导致服务看起来正常,但和redis相关的功能却没有更新。记录下这个异常定位解决的过程。
登录到redis里面,发现redis也是运行正常的,且能正常获取。所以进入到了服务端里面,获取jvm线程进行具体分析,看到有很多个线程栈如下:

## 定位分析过程
- **`pool-4-thread-1`**:
线程名称,表明该线程属于线程池 `pool-4` 的第一个工作线程(线程池通常由 `ThreadPoolExecutor` 管理)。
- **`Id=211`**:
JVM 内部分配的线程唯一标识符(非操作系统线程ID)。
- ### **CPU 时间统计**
```csharp
cpu=692644037380 ns usr=644020000000 ns
```
- **`cpu=692644037380 ns`**:
线程从启动至今消耗的 **总 CPU 时间**(包括内核态和用户态),单位为纳秒(≈ 692.64 秒)。
- **`usr=644020000000 ns`**:
线程在 **用户态(User Mode)** 消耗的 CPU 时间(≈ 644.02 秒)。
**差值意义**:`cpu - usr ≈ 48.62秒` 为线程在内核态(Kernel Mode)的耗时,通常由系统调用(如 I/O、锁竞争)引起。
* * *
### **线程阻塞与等待统计**
```csharp
blocked 2294 for -1 ms waited 28442 for -1 ms
```
- **`blocked 2294`**:
线程因 **竞争锁(synchronized)** 而被阻塞的次数(总计 2294 次)。
- **`for -1 ms`**:
阻塞时间的统计方式,`-1 ms` 表示未记录具体阻塞时长(需启用 JVM 参数 `-XX:+PrintBlocked` 获取)。
- **`waited 28442`**:
线程在 **等待条件触发**(如 `Object.wait()` 或 `Condition.await()`)的次数(总计 28442 次)。
- **`for -1 ms`**:
等待时间的统计方式,`-1 ms` 表示未记录具体等待时长(需启用 `-XX:+PrintWait` 获取)。
* * *
### **线程状态与堆栈跟踪**
```csharp
java.lang.Thread.State: WAITING
at sun.misc.Unsafe.park(Native Method)
- waiting on (a java.util.concurrent.ThreadPoolExecutor$Worker@5cf37a65)
```
- **`Thread.State: WAITING`**:
线程处于 **无限期等待** 状态,通常由以下操作触发:
- `Object.wait()`(无超时参数)。
- `LockSupport.park()`。
- `Condition.await()`(无超时参数)。
- **`sun.misc.Unsafe.park(Native Method)`**:
线程通过 `LockSupport.park()` 进入阻塞状态,底层调用 `Unsafe.park()`。
- **`waiting on (a java.util.concurrent.ThreadPoolExecutor$Worker@5cf37a65)`**:
线程正在等待 `ThreadPoolExecutor.Worker` 对象(线程池工作线程的封装)关联的条件变量(如任务队列非空)。
* * *
### **关键堆栈分析**
```csharp
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
```
- **核心路径**:
1. 线程从 `LinkedBlockingQueue.take()` 尝试获取任务。
2. 若队列为空,调用 `ConditionObject.await()` 进入等待。
3. 最终通过 `LockSupport.park()` 挂起线程,直到新任务到达。
* * *
### **性能问题诊断**
#### **1. 高 `waited` 次数(28442 次)**
- **可能原因**:
- 线程池任务队列长期为空,工作线程频繁等待新任务。
- 任务生产速度不足(如上游系统吞吐量低)。
- 线程池配置不合理(核心线程数过多,超出实际需求)。
#### **2. 高 `blocked` 次数(2294 次)**
- **可能原因**:
- 线程池内部锁竞争(如 `Worker` 线程争用任务队列)。
- 共享资源(如数据库连接池)的同步访问冲突。
#### **3. CPU 时间分配**
- **用户态耗时占比**:
`usr / cpu ≈ 644.02 / 692.64 ≈ 93%`,表明线程主要执行用户代码,而非系统调用。若应用为计算密集型,此比例为正常现象。
* * *
### **优化建议**
#### **1. 线程池配置优化**
- **调整核心线程数**:
若队列长期为空,减少 `corePoolSize`,避免线程闲置。
```csharp
new ThreadPoolExecutor(
corePoolSize, // 根据负载动态调整(如使用动态线程池框架)
maxPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(capacity)
);
```
#### **2. 任务队列监控**
- **检查队列容量**:
若使用无界队列(如 `LinkedBlockingQueue` 未指定容量),可能导致内存溢出,建议改为有界队列。
- **监控队列堆积**:
通过 JMX 或 `ThreadPoolExecutor` 的 `getQueue().size()` 实时观察任务积压情况。
#### **3. 减少锁竞争**
- **使用无锁数据结构**:
替换 `LinkedBlockingQueue` 为 `ConcurrentLinkedQueue`(需配合非阻塞任务调度逻辑)。
- **分离读写操作**:
若共享资源访问频繁,使用读写锁(`ReentrantReadWriteLock`)替代独占锁。
## 问题解决:
根据截图中的线程栈调用过程,可以定位到项目代码执行调用的地方,发现调用的地方是频繁批量更新redis缓存值得,且每次都是单独一条设置更新得。因此很快推测出来,是这个调用得地方在频繁更新redis缓存值时,导致服务中redis得连接数不够了,因此将代码中更新redis值得方式,使用管道得方式进行更新设置,问题得以解决。
本文来自投稿,不代表本站立场,如若转载,请注明出处:http//www.knowhub.vip/share/2/3233
- 热门的技术博文分享
- 1 . ESP实现Web服务器
- 2 . 从零到一:打造高效的金仓社区 API 集成到 MCP 服务方案
- 3 . 使用C#构建一个同时问多个LLM并总结的小工具
- 4 . .NET 原生驾驭 AI 新基建实战系列Milvus ── 大规模 AI 应用的向量数据库首选
- 5 . 在Avalonia/C#中使用依赖注入过程记录
- 6 . [设计模式/Java] 设计模式之工厂方法模式
- 7 . 5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
- 8 . SQL 中的各种连接 JOIN 的区别总结!
- 9 . JavaScript 中防抖和节流的多种实现方式及应用场景
- 10 . SaltStack 远程命令执行中文乱码问题
- 11 . 推荐10个 DeepSeek 神级提示词,建议搜藏起来使用
- 12 . C#基础:枚举、数组、类型、函数等解析
- 13 . VMware平台的Ubuntu部署完全分布式Hadoop环境
- 14 . C# 多项目打包时如何将项目引用转为包依赖
- 15 . Chrome 135 版本开发者工具(DevTools)更新内容
- 16 . 从零创建npm依赖,只需执行一条命令
- 17 . 关于 Newtonsoft.Json 和 System.Text.Json 混用导致的的序列化不识别的问题
- 18 . 大模型微调实战之训练数据集准备的艺术与科学
- 19 . Windows快速安装MongoDB之Mongo实战
- 20 . 探索 C# 14 新功能:实用特性为编程带来便利