最近线上服务出现了一段时间的无法响应,在此总结一下问题的排查过程。
监控信息
监控显示CPU和内存没有异常波动,TCP连接中有大量的CLOSE_WAIT状态的连接。
看一下TCP连接断开的过程:
也就是说客户端发起了断开连接的包,服务端收到数据后状态变更为CLOSE_WAIT,这时需要等待服务端处理完连接中未发送完的数据之后发送FIN给客户端,但是服务端此时可能因为各种原因,中断在了这个环节,导致操作系统的连接数被CLOSE_WAIT占用,没有足够资源响应客户端请求。所以接下来就需要查清楚服务端到底在忙什么,为什么不继续完成断开连接的四次挥手过程。
线程栈
对于Java应用,可以利用JDK提供的工具查看线程的栈。
jstack -l pid
其中pid是JVM进程的ID
打印出的栈信息中可以发现大量类似上图的WAITING状态的线程,线程在以下几种情况会进入WAITING状态:
Object的wait方法,并且没有使用timeout参数;
Thread的join方法,没有使用timeout参数;
LockSupport的park方法。
如果一个线程调用了一个对象的wait方法,那么这个线程就会处于waiting状态直到另外一个线程调用这个对象的notify或者notifyAll方法后才会解除这个状态。
具体到此处的问题,可以理解为数据库连接池中获取连接时,没有可用连接,此时调用notEmpty.await();等待有连接释放回收到连接池或创建新的连接后notify当前线程再来获取连接。所以此时问题的根本原因是数据库连接池耗尽,一直在wait可用连接,线程无法继续执行下去。
数据库连接池的配置
我的应用是基于springboot开发的,在技术社区也查到了一些关于springboot的默认数据库连接配置可能会存在潜在风险。数据连接池默认配置带来的坑
简单来说,默认的数据连接配置,可能会导致连接池中的连接是不可用的,但是仍在连接池中充数,并不会被释放。增加了以下几个参数,DONE!
testWhileIdle: true
validationQuery: SELECT 1
timeBetweenEvictionRunsMillis: 60000