系统的用户操作响应缓慢,数据库很多异常的会话,maxWait这个属性值Druid默认值是-1,导致连接池被占满,数据库连不上一直占用
故障现象
问题影响时间:7月12号 08:21点-09:30点
现象:无法进入系统,已进入系统的用户操作响应缓慢
故障处理过程
08:52 A开始反馈 xx-prod 进不去,数据库很多异常的会话
08:57-09:15 B进通过grafna和数据库库行了问题的定位,重启应用
09:18 实例重启成功后,问题依然无法正常服务
09:20-09:25 C通过慢请求定位到申请单查询接口特别慢,定位到是阿里商旅服务的异常
09:30 D 重启阿里商旅服务,重启后,实例开始正常工作。
故障原因
- 阿里商旅服务异常。通过分析stack,主要是拿不到数据库的链接,导致ice 线程池被占满,导致其他的请求无法请求。导致代码拿不到连接的原因如下:
- 分析堆栈信息发现大部分的线程都是阻塞的状态
b. 分析具体的某个阻塞的线程,发现是因为事务导致的拿不到链接导致的
c. 通过堆栈信息,可以发现是事务的注解不规范导致的。不应该在类上添加事务的注解,应该加在具体的方法上。
- ice 框架的超时时间设置没有生效。阿里商旅这个服务比较特殊,依赖了钉钉的sdk,只用的jdk1.8,所有的jar包有没有升级到最新的,目前service-common 已经更新到3.2.23,而阿里商旅用的还是1.5.6 。
- 代码中的逻辑不是最优的,报销单关联申请单明细的时候,遍历申请单的明细接口,返回了申请单的所有信息,可以修改为只返回前端需要的信息。
首先从stack中可以看出来:
这里大量的ICE线程在进入了DruidDataSource#takeLast方法后就进入了WAITING的状态;
因此查看了下这个服务用到的Druid版本(1.1.21)对应的代码:
可以看到notEmpty#await方法让当前线程一直进入WAITING状态等待唤醒。
进入这个分支,说明DruidConnectionHolder连接池容器中没有连接了,因此一直等待有空闲线程去唤醒,同时,这里没有设置超时时间,理论上会一直占用着当前线程,因此线上会出现ICE线程池满的情况(其实是大量线程都在waiting)。
回到最开始的入口方法DruidDataSource#takeLast,进入这个方法的前提是maxWait小于0(默认是-1),如果我们设置maxWait > 0,就会进入有超时时间的分支,线程会尝试获得锁,超过超时时间直接返回null,可以避免线程一直WAITING
maxWait这个属性值Druid默认值是-1,可以显式通过设置maxWait的方式来优化,设置一个合理的预期时间值,释放ICE线程去做其他事不要因为数据库连不上一直占用
反思与改进方案
系统的用户操作响应缓慢,数据库很多异常的会话,maxWait这个属性值Druid默认值是-1,导致连接池被占满,数据库连不上一直占用