最近系统偶尔出现无法登陆,不能响应请求的情况。
上服务器使用Jps,Jstack将线程堆栈信息输出到文件里面,然后从服务器上把文件取下来通过fastthread.io把文件上传分析。
不得不说用这个网址分析方便了很多,不然几百个线程看得头皮发麻。
可以看到300多个线程在WAITING状态。
随便点一个进去看看
基本上300多个等待的线程都很类似,都是在等数据库连接。
这些个线程的源头一半是系统定时任务,一半是接口调用的关于角色的Spring Security权限验证。
系统中Tomcat最大线程配置的500,但是数据库连接池的最大连接数配置的80.
可是系统对外开放的接口并没有角色的限制。没办法只看看系统中Spring Security的配置,发现现在的配置是判断有没有角色限制是通过数据库验证的(通过修改FilterSecurityInterceptor的securityMetadataSource)。这个配置就是不管什么请求,都会去数据取对应请求地址的需要的角色。其他请求倒是没啥事,毕竟没几个人用,但是对外的那个接口就不一样了。所以把这里这个没用的验证去掉。
另外定时任务每分钟跑一次,还是使用@Async异步的,使用的默认的线程池,无限开线程,线程存活时间还长,这谁顶得住呀。所以定时任务这里应该改成@Async(自定义的线程池)。
然而事情并不是这么简单,上面的做法只是在数据库连接池拿不到连接的时候,减少等待连接的线程,关键问题还是要找到占用连接的线程。
首先,我开启了druid的监控。
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.stat-view-servlet.enabled=true
复制代码然后意外的发现目前removeAbandoned设置的false,将其改为true
spring.datasource.druid.removeAbandoned=true
复制代码
改完之后就发现了有些任务出现了connection holder is null的错误。
这说明那些任务很长时间内都需要操作数据库,在连接池回收之后提交事务倒是出现异常。
最后从代码里发现,定时任务的所有操作都在一个事务里,一个任务几十分钟,长于removeAbandonedTimeout的时间,所以出问题了。所以,定时的大粒度事务应该去掉。