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

 

一、前言

        记录一次服务假死的整个排查过程,服务基础为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

已标记关键词 清除标记
项目使用spring+springmvc+hibernate,数据库使用oracle11.2.0.1.0,允许的最大连接数为300,数据库服务器防火墙是关闭的。项目中有个定时任务(每天凌晨执行)使用jdbcTemplate同步数据。druid连接池异常信息如下: ``` [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-3] WARN com.alibaba.druid.pool.DruidDataSource - get connection timeout retry : 1 [2019-11-02 03:00:02.002] [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-3] ERROR org.quartz.core.JobRunShell - Job DEFAULT.syncSyldDataDetail threw an unhandled Exception: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 1000, active 0, maxActive 350, creating 1 at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:81) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:371) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:578) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at com.aqkk.task.SyncSyldData.start(SyncSyldData.java:33) ~[classes/:?] at com.aqkk.task.SyncSyldDataJob.executeInternal(SyncSyldDataJob.java:17) ~[classes/:?] at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75) ~[spring-context-support-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [quartz-2.3.0.jar:?] at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [quartz-2.3.0.jar:?] Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 1000, active 0, maxActive 350, creating 1 at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1512) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1255) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5007) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.filter.stat.StatFilter.dataSource_getConnection(StatFilter.java:680) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5003) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1233) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1225) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:90) ~[druid-1.1.10.jar:1.1.10] at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:151) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:115) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:78) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] ... 7 more [2019-11-02 03:00:02.002] [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-3] ERROR org.quartz.core.ErrorLogger - Job (DEFAULT.syncSyldDataDetail threw an exception. org.quartz.SchedulerException: Job threw an unhandled exception. at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [quartz-2.3.0.jar:?] at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [quartz-2.3.0.jar:?] Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 1000, active 0, maxActive 350, creating 1 at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:81) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:371) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:578) ~[spring-jdbc-5.0.8.RELEASE.jar:5.0.8.RELEASE] at com.aqkk.task.SyncSyldData.start(SyncSyldData.java:33) ~[classes/:?] at com.aqkk.task.SyncSyldDataJob.executeInternal(SyncSyldDataJob.java:17) ~[classes/:?] at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75) ~[spring-context-support-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.3.0.jar:?] ... 1 more Caused by: com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 1000, active 0, maxActive 350, creating 1 at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1512) ~[druid-1.1.10.jar:1.1.10] at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1255) ~[druid-1.1.10.jar:1.1.10] ``` druid配置信息如下: ``` <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${db.jdbcUrl}"/> <property name="username" value="${db.user}"/> <property name="password" value="${db.password}"/> <property name="driverClassName" value="${db.driver}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="1"/> <property name="minIdle" value="20"/> <property name="maxActive" value="350"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="1000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="10000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="30000"/> <property name="testWhileIdle" value="true"/> <property name="validationQuery" value="SELECT 1 FROM DUAL"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 --> <property name="poolPreparedStatements" value="true"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/> <!-- 开启Druid的监控统计功能 --> <property name="filters" value="stat" /> </bean> ``` 为啥凌晨执行定时任务获取不到连接,9点上班系统又是正常运行的呢?
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页