一场因数据库连接池配置引发的血案

数据库连接池配置引发的危机

车祸现场:

xxx系统由于业务需求,所要部署的定时任务变的越来越多,于是决定利用Quartz框架来做一个任务调度管理模块(采用集群模式,并持久化任务调度信息,数据保存于mysql5.6)。
翌日,模块开发完成,顺利上线,但是上线一天后,服务报error了,如下:

JobStoreTX - MisfireHandler: Error handling misfires: Database error recovering from misfires.
Another error has occurred 
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed. 
which will not be reported to listeners!

于是排查问题,然后解决问题,第二次上线,这次上线新增了如下配置:

spring.datasource.test-while-idle: true
spring.datasource.time-between-eviction-runs-millis: 60000
spring.datasource.validation-query: SELECT 1

服务上线,运行正常

第二天运行正常
第三天运行正常
呵呵,小问题,手到擒来
运行一周后…
产品经理:高高呀,我怎么觉得咱们服务响应速度有点慢呀
我:哦?是吗,我去查查哈,可能是数据库的数据量有点大了
运行1.5周后…
产品经理:高高,服务响应速度越来越慢了,你那边排查完了没
我(顿时一惊,靠,不会内存泄漏了吧!):好了,马上就好啦

数日后,高高经过一番排查后,终于锁定并解决了问题。

排查历程:

问题1:
数据库空闲连接自动断开,导致任务运行异常发生
分析解决:

MySQL服务器默认的“wait_timeout”是28800秒即8小时,意味着如果一个连接的空闲时间超过8个小时,MySQL将自动断开该连接,而连接池却认为该连接还是有效的(Quartz定时任务使用的是默认是maxIdleTime=0,永不丢弃),所以当应用申请使用该连接时,就会导致异常发生。
由于我们的数据库连接池没有配置连接有效性检查,导致quartz使用的连接线程超时无效,所以我做了如下配置,定期使用连接池内的连接,使得它们不会因为闲置超时而被 MySQL 断开。
spring.datasource.test-while-idle: true
spring.datasource.time-between-eviction-runs-millis: 60000
spring.datasource.validation-query: SELECT 1
于是数据库连接超时断开的问题,就这样被解决了

问题2:
服务整体响应速度变慢
分析解决:
1.排查内存泄漏问题
查看当前内存使用率,发现内存使用率仅有30%
考虑到刚刚上线的原因,然后查看内存使用率趋势图,并非内存泄漏的模样,于是内存泄漏的可能性可以排除掉了
2.查看cpu使用率
经过排查cpu使用率正常
3.给服务打log,定位耗时长的代码
观察日志发现耗时长的地方在sql查询
4.查看数据库连接配置(数据库连接池默认使用Tomcat connection pool)

参数 参数说明
spring.datasource.test-while-idle (boolean) The indication of whether objects will be validated by the idle object evictor (if any). If an object fails to validate, it will be dropped from the pool. The default value is false and this property has to be set in order for the pool cleaner/test thread is to run (also see timeBetweenEvictionRunsMillis)
spring.datasource.time-between-eviction-runs-millis (int) The number of milliseconds to sleep between runs of the idle connection validation/cleaner thread. This value should not be set under 1 second. It dictates how often we check for idle, abandoned connections, and how often we validate idle connections. The default value is 5000 (5 seconds).
spring.datasource.validation-query (String) The SQL query that will be used to validate connections from this pool before returning them to the caller. If specified, this query does not have to return any data, it just can’t throw a SQLException. The default value is null. If not specified, connections will be validation by the isValid() method. Example values are SELECT 1(mysql), select 1 from dual(oracle), SELECT 1(MS Sql Server)

仔细看了一下配置说明,初步推测可能是test-while-idle和time-between-eviction-runs-millis造成的影响,原因可能如下(此处是自己猜测,还未做深入探究 ):

  1. 检查空闲,验证空闲连接,删除连接连接的频率过高,占用服务大量资源。(可能性较低,因为资源监控并未发现系统的资源被过多占用)
  2. 每次检查空闲连接状态后(运行 select 1),使得空闲连接变得不空闲,重新开始计算其空闲时间,进而导致大量的无用连接不被释放,进而导致查询变慢(可能性较大)

5.解决问题
删除spring.datasource.test-while-idle 和 spring.datasource.time-between-eviction-runs-millis参数,新增spring.datasource.test-on-borrow=true、spring.datasource.validation-interval=60000。

参数 参数说明
spring.datasource.test-on-borrow (boolean) The indication of whether objects will be validated before being borrowed from the pool. If the object fails to validate, it will be dropped from the pool, and we will attempt to borrow another. In order to have a more efficient validation, see validationInterval. Default value is false
spring.datasource.validation-interval (long) avoid excess validation, only run validation at most at this frequency - time in milliseconds. If a connection is due for validation, but has been validated previously within this interval, it will not be validated again. The default value is 3000 (3 seconds).

test-on-borrow:在从池中借用对象之前验证对象,若对象无法验证,将其从池中删除,并尝试借用另一个。
validation-interval: 连接在进行验证时,如果之前已在此时间间隔内进行了验证,则不会再次进行验证。
validation-interval值设置为60000,是因为我们的数据库连接池使用了dbProxy,dbProxy空闲等待的时间比数据库的空闲等待时间更短,从日志来看空闲断开的时间有时候会低至2min,故设置为60000ms。

最开始其实我是拒绝使用test-on-borrow的,因为网上查资料时看到说开启这个参数后会影响性能,但是具体怎么影响没有说。
我想性能影响的点应该在:每次获取连接对象前的验证操作,当数据库访问量比较大的时候,才会表现出性能问题。但考虑到这个服务主要是我们的管理后台,不会有过高的访问量,所以使用了这个参数。
再次上线并观察了一段时间后,可以做出定论:服务响应速度变慢的问题解决了,性能问题并未感知到。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章