手动创建DruidDataSource失败后死循环以及程序卡住的问题

druid的版本:1.1.20

这个问题在druid的github源码上一直是个open的issue,见3488和3357等

druid的配置,注意timeBetweenEvictionRunsMillis参数,这是造成卡的原因。这个参默认值为60 * 1000L(60秒),单位是毫秒

# Druid配置
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=false
spring.datasource.druid.testOnReturn=false
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.filters=stat,wall,log4j
spring.datasource.druid.connectionProperties=druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,10.53.145.28
#spring.datasource.druid.stat-view-servlet.deny=10.53.145.28
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=123456
现象:最后在使用Druid配置多数据源,需要手动创建DruidDataSource,在创建DruidDataSource失败后会不停的打印日志,如下(看时间一直在变,后台日志一直在刷)

手动创建DruidDataSource失败后死循环以及程序卡住的问题_第1张图片

网上的解决方法:在配置数据源时加上setBreakAfterAcquireFailure(true);,如下
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
    	DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
    	dataSource.setBreakAfterAcquireFailure(true);
        return dataSource;
    }
原理:看报错信息类DruidDataSource.java:2715,下面有一段源码,在2721行处当错误次数大于connectionErrorRetryAttempts且timeBetweenConnectErrorMillis 大于0,connectionErrorRetryAttempts默认值为1,timeBetweenConnectErrorMillis 默认是500,可以通过配置项spring.datasource.druid.timeBetweenEvictionRunsMillis配置,从源码的2733行可以看到设置breakAfterAcquireFailure为true则会break。日志狂刷的问题就终止了,但是这次拿到的connection是空的。为下一个问题埋了坑。

手动创建DruidDataSource失败后死循环以及程序卡住的问题_第2张图片

这个解决了日志狂刷的问题,但是程序卡住了,没响应没返回
2019-12-23 18:59:55.806  INFO 19724 --- [nio-8087-exec-1] o.a.c.c.C.[.[localhost].[/rwdemo]        : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-23 18:59:55.806  INFO 19724 --- [nio-8087-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-12-23 18:59:55.815  INFO 19724 --- [nio-8087-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 9 ms
切换到slave2
feeId = 1
2019-12-23 18:59:55.967  INFO 19724 --- [nio-8087-exec-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
2019-12-23 18:59:56.321 ERROR 19724 --- [eate-1736720965] com.alibaba.druid.pool.DruidDataSource   : create connection SQLException, url: jdbc:mysql://10.53.56.7:3306/blog?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true, errorCode 1045, state 28000

java.sql.SQLException: Access denied for user 'root'@'10.53.56.1' (using password: YES)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:835) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:455) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:207) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1643) ~[druid-1.1.20.jar:1.1.20]
	at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1709) ~[druid-1.1.20.jar:1.1.20]
	at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2715) ~[druid-1.1.20.jar:1.1.20]

2019-12-23 18:59:56.329 ERROR 19724 --- [eate-1736720965] com.alibaba.druid.pool.DruidDataSource   : create connection SQLException, url: jdbc:mysql://10.53.56.7:3306/blog?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true, errorCode 1045, state 28000

java.sql.SQLException: Access denied for user 'root'@'10.53.56.1' (using password: YES)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:835) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:455) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:207) ~[mysql-connector-java-8.0.13.jar:8.0.13]
	at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1643) ~[druid-1.1.20.jar:1.1.20]
	at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1709) ~[druid-1.1.20.jar:1.1.20]
	at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2715) ~[druid-1.1.20.jar:1.1.20]

2019-12-23 18:59:56.330  INFO 19724 --- [eate-1736720965] c.a.druid.pool.DruidAbstractDataSource   : {dataSource-1} failContinuous is true
程序卡住了,没返回也没响应

手动创建DruidDataSource失败后死循环以及程序卡住的问题_第3张图片

使用JVM工具(jconsole或jvisualvm)查看,线程卡在以下地方等待

手动创建DruidDataSource失败后死循环以及程序卡住的问题_第4张图片

原因:看线程卡住的地方,在DruidDataSource.java:2786行,源码如下

手动创建DruidDataSource失败后死循环以及程序卡住的问题_第5张图片

可以看到当timeBetweenEvictionRunsMillis>0时暂停timeBetweenEvictionRunsMillis,否则暂停1秒。timeBetweenEvictionRunsMillis的默认值是60 * 1000(60秒) 尝试了以下解决方法都不行,加上以下配置
# 修改timeBetweenEvictionRunsMillis参数,减少时间
spring.datasource.druid.timeBetweenEvictionRunsMillis=6000

# DruidDataSource.java:2724行,如果设置了该配置为true
spring.datasource.druid.fail-fast=true
spring.datasource.druid.testOnBorrow=true

结论

该问题无解,可以换其它连接池,比如springboot 2.X默认的HikariCP,DBCP或tomcat pool

你可能感兴趣的:(druid)