记录一次服务假死的整个排查过程,服务基础为spring boot + druid + 多数据源切换,在请求过多(尤其是长事务请求)时,服务出现请求无响应的状况,之前未完结的查询也没有任何返回结果。
二、定位问题原因问题出现时,表现如下图,后台无任何报错,sql语句戛然而止,后续的查询被中断。这时如果再次发起某个请求,后台服务处于大部分时间不能收到新的请求的状态,或者偶尔可收到请求但不会执行crud。经过一段时间后,日志输出了session校验的内容,此时我推测服务并不是真的宕机,而是处于假死状态。
druid配置如下
看了下配置文件,最大连接池数量设置为100,但重现问题的过程不需要特别多的请求,稳妥起见,将最大数量改为200,问题没有解决,初步推测不是因为请求达到了连接池上限。
CPU和内存均无异常。
期间还优化过业务逻辑,甚至将同步请求改为异步请求,都无济于事。一筹莫展之际,想起了druid自带的监控功能。打开监控页面,找到了一处令我怀疑的地方。数据源中的逻辑打开和逻辑关闭次数起初是一致的,随着查询次数增多,逻辑关闭次数小于逻辑打开次数,于是我怀疑数据库连接池出现了泄露的情况,根据URI监控中显示的情况,jdbc出错数刚好等于逻辑打开与逻辑关闭次数的差,也就是说,很有可能由于jdbc出错导致数据库连接池未正确关闭。
(图片为部分截图,下面还有个1没有截到)
按着这个思路,对测试部分代码进行了排查,无果。后来根据druid官方的文档,找到了下面这段话。
从这段话中可以看出,判断是否是泄露应该在URI监控中,点击URI进入详情页面,查看打开和关闭的数量是否相等。于是我在逻辑连接打开和逻辑连接关闭次数有巨大差异的情况下,对每一个URI都进行了核查,所有URI详情中,连接池获取连接次数都是等于连接池关闭连接次数的,理论上证明数据库连接池并无泄露。
前面图中druid的文档中还提到了removeAbandoned等三个属性用以检测数据库连接池泄露,于是我将这三个属性写在了yml里。
一通华丽丽的操作下来,服务依旧被玩坏,然后打开了druid的监控,找到了数据源页面中的ActiveConnectionStackTrace。点开,没数据???控制台也没输出日志??? 网上找了一些文章,然后大胆的怀疑是不是druid的配置没生效?再看一下druid运行时的数据源,果不其然。初始化连接大小、最小空闲连接数、最大连接数、超时时间等等等等,除了数据库指向是生效的,其他配置使用的都是缺省值。所以我即使把最大数量从100改成了200,依然是没几个请求就爆炸了。
下面开始着手解决配置没生效的问题。由于项目是多数据源切换,于是找到了这个配置文件,尝试着改造了一下,将yml中的配置set到了DruidDataSource对象中。
(下图仅展示了与本文有关的内容)
-
@Configuration
-
public class DruidConfig {
-
@Value("${spring.datasource.druid.initial-size}")
-
private int initialSize;
-
@Value("${spring.datasource.druid.min-idle}")
-
private int minIdle;
-
@Value("${spring.datasource.druid.max-active}")
-
private int maxActive;
-
@Value("${spring.datasource.druid.max-wait}")
-
private int maxWait;
-
@Value("${spring.datasource.druid.pool-prepared-statements}")
-
private boolean poolPreparedStatements;
-
@Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
-
private int maxPoolPreparedStatementPerConnectionSize;
-
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
-
private int timeBetweenEvictionRunsMillis;
-
@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
-
private int minEvictableIdleTimeMillis;
-
@Value("${spring.datasource.druid.test-while-idle}")
-
private boolean testWhileIdle;
-
@Value("${spring.datasource.druid.test-on-borrow}")
-
private boolean testOnBorrow;
-
@Value("${spring.datasource.druid.test-on-return}")
-
private boolean testOnReturn;
-
@Value("${spring.datasource.druid.remove-abandoned}")
-
private boolean removeAbandoned;
-
@Value("${spring.datasource.druid.remove-abandoned-timeout}")
-
private int removeAbandonedTimeout;
-
@Value("${spring.datasource.druid.log-abandoned}")
-
private boolean logAbandoned;
-
/**
-
* 设置数据库连接池
-
*
-
* @author sunbin
-
* @since 2020年4月21日
-
* @version 2020年4月21日
-
* @param dataSource
-
*/
-
private void setDruidDataSource(DruidDataSource dataSource) {
-
dataSource.setInitialSize(initialSize);
-
dataSource.setMinIdle(minIdle);
-
dataSource.setMaxActive(maxActive);
-
dataSource.setMaxWait(maxWait);
-
dataSource.setPoolPreparedStatements(poolPreparedStatements);
-
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
-
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
-
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
-
dataSource.setTestWhileIdle(testWhileIdle);
-
dataSource.setTestOnBorrow(testOnBorrow);
-
dataSource.setTestOnReturn(testOnReturn);
-
dataSource.setRemoveAbandoned(removeAbandoned);
-
dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout);
-
dataSource.setLogAbandoned(logAbandoned);
-
}
-
/**
-
* 默认数据源
-
*
-
* @author 89390
-
* @since 2019年4月15日
-
* @version 2019年4月15日
-
* @return
-
*/
-
@Bean
-
@ConfigurationProperties("spring.datasource.druid.master")
-
public DataSource masterDataSource() {
-
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
-
setDruidDataSource(dataSource);
-
return dataSource;
-
}
-
}
重启服务,查看druid monitor,与yml中配置的一致了,即证明此时配置已经生效,再次测试,服务假死问题解决。
四、分析项目中,spring boot版本为2.0.3.RELEASE
druid版本为1.1.10
考虑到版本等问题,虽然我们在yml中配置了druid连接池的其它属性,但是不会生效。因为默认是使用的java.sql.Datasource的类来获取属性的,有些属性datasource没有。如果我们想让配置生效,需要手动创建Druid的配置文件。由于项目中多数据源的功能,已经有了Druid的配置文件,所以无需新建,适当修改即可。
五、参考https://github.com/alibaba/druid/wiki/%E8%BF%9E%E6%8E%A5%E6%B3%84%E6%BC%8F%E7%9B%91%E6%B5%8B
https://www.cnblogs.com/SimpleWu/p/10049825.html