记一次druid连接池配置问题引发服务假死的定位、分析、解决过程

 

一、前言

        记录一次服务假死的整个排查过程,服务基础为spring boot + druid + 多数据源切换,在请求过多(尤其是长事务请求)时,服务出现请求无响应的状况,之前未完结的查询也没有任何返回结果。

二、定位问题原因

        问题出现时,表现如下图,后台无任何报错,sql语句戛然而止,后续的查询被中断。这时如果再次发起某个请求,后台服务处于大部分时间不能收到新的请求的状态,或者偶尔可收到请求但不会执行crud。经过一段时间后,日志输出了session校验的内容,此时我推测服务并不是真的宕机,而是处于假死状态。

   记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第1张图片

    druid配置如下

        记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第2张图片

        看了下配置文件,最大连接池数量设置为100,但重现问题的过程不需要特别多的请求,稳妥起见,将最大数量改为200,问题没有解决,初步推测不是因为请求达到了连接池上限。

        CPU和内存均无异常。

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第3张图片

         期间还优化过业务逻辑,甚至将同步请求改为异步请求,都无济于事。一筹莫展之际,想起了druid自带的监控功能。打开监控页面,找到了一处令我怀疑的地方。数据源中的逻辑打开和逻辑关闭次数起初是一致的,随着查询次数增多,逻辑关闭次数小于逻辑打开次数,于是我怀疑数据库连接池出现了泄露的情况,根据URI监控中显示的情况,jdbc出错数刚好等于逻辑打开与逻辑关闭次数的差,也就是说,很有可能由于jdbc出错导致数据库连接池未正确关闭。

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第4张图片

(图片为部分截图,下面还有个1没有截到) 

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第5张图片

        按着这个思路,对测试部分代码进行了排查,无果。后来根据druid官方的文档,找到了下面这段话。

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第6张图片         

        从这段话中可以看出,判断是否是泄露应该在URI监控中,点击URI进入详情页面,查看打开和关闭的数量是否相等。于是我在逻辑连接打开和逻辑连接关闭次数有巨大差异的情况下,对每一个URI都进行了核查,所有URI详情中,连接池获取连接次数都是等于连接池关闭连接次数的,理论上证明数据库连接池并无泄露。

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第7张图片

        前面图中druid的文档中还提到了removeAbandoned等三个属性用以检测数据库连接池泄露,于是我将这三个属性写在了yml里。

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第8张图片

        一通华丽丽的操作下来,服务依旧被玩坏,然后打开了druid的监控,找到了数据源页面中的ActiveConnectionStackTrace。点开,没数据???控制台也没输出日志??? 网上找了一些文章,然后大胆的怀疑是不是druid的配置没生效?再看一下druid运行时的数据源,果不其然。初始化连接大小、最小空闲连接数、最大连接数、超时时间等等等等,除了数据库指向是生效的,其他配置使用的都是缺省值。所以我即使把最大数量从100改成了200,依然是没几个请求就爆炸了。

记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第9张图片

 三、解决方案

        下面开始着手解决配置没生效的问题。由于项目是多数据源切换,于是找到了这个配置文件,尝试着改造了一下,将yml中的配置set到了DruidDataSource对象中。

      记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第10张图片

(下图仅展示了与本文有关的内容) 

@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

        记一次druid连接池配置问题引发服务假死的定位、分析、解决过程_第11张图片

        考虑到版本等问题,虽然我们在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

你可能感兴趣的:(解决方案,日常问题,druid,java,druid,spring,boot,服务假死)