在项目开发中,遇到druid在连接失败后一直重试,导致接口不会返回结果、日志不停打印,跟踪源码发现druid的失败重试在DruidDataSource类中,位置如下:
public void run() {
DruidDataSource.this.initedLatch.countDown();
long lastDiscardCount = 0L;
int errorCount = 0;
while(true) {
...
try {
connection = DruidDataSource.this.createPhysicalConnection();
} catch (SQLException var29) {
DruidDataSource.LOG.error("create connection SQLException, url: " + DruidDataSource.this.jdbcUrl + ", errorCode " + var29.getErrorCode() + ", state " + var29.getSQLState(), var29);
++errorCount;
// 如果重试次数达到后进入if
if (errorCount > DruidDataSource.this.connectionErrorRetryAttempts && DruidDataSource.this.timeBetweenConnectErrorMillis > 0L) {
DruidDataSource.this.setFailContinuous(true);
if (DruidDataSource.this.failFast) {
DruidDataSource.this.lock.lock();
try {
DruidDataSource.this.notEmpty.signalAll();
} finally {
DruidDataSource.this.lock.unlock();
}
}
// breakAfterAcquireFailure为true结束掉while循环
if (DruidDataSource.this.breakAfterAcquireFailure) {
break;
}
try {
Thread.sleep(DruidDataSource.this.timeBetweenConnectErrorMillis);
} catch (InterruptedException var27) {
break;
}
}
} catch (RuntimeException var30) {
DruidDataSource.LOG.error("create connection RuntimeException", var30);
DruidDataSource.this.setFailContinuous(true);
continue;
}
...
}
}
是否中断重试 breakAfterAcquireFailure 默认为false;
重试次数 connectionErrorRetryAttempts默认为1;
看完源码后找到问题所在,将connectionErrorRetryAttempts设置为失败后需要重试的次数,并把breakAfterAcquireFailure改为true就可以了
需要在创建DruidDataSource时添加,直接在配置文件内是无效的,在DruidDataSource源码中可以看到,它并没有去读取这两个配置:
public void configFromPropety(Properties properties) {
String property = properties.getProperty("druid.name");
if (property != null) {
this.setName(property);
}
property = properties.getProperty("druid.url");
if (property != null) {
this.setUrl(property);
}
property = properties.getProperty("druid.username");
if (property != null) {
this.setUsername(property);
}
property = properties.getProperty("druid.password");
if (property != null) {
this.setPassword(property);
}
...
解决办法,可以在创建数据源DataSource的时候将配置写入:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 主库数据源
master:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/database?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: username
password: password
slave:
# 从数据源开关/默认关闭
enabled: true
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/database?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: username
password: password
# 初始连接数
initialSize: 20
# 最小连接池数量
minIdle: 20
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 3000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
# 重连失败后在重试规定次数后 终止重试
breakAfterAcquireFailure: true
# 失败重试次数
connectionErrorRetryAttempts: 5
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: username
login-password: password
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
// 在注入bean时配置
datasource.setBreakAfterAcquireFailure(true);
datasource.setConnectionErrorRetryAttempts(3);
return druidProperties.dataSource(dataSource);
}
配置完成后重启生效,但是如果数据库连接失败我们还是很有必要进行重试的,可以通过设置连接等待超时时间 maxWait 来让接口快速返回,源码部分在DruidDataSource.class中的getConnectionDirect(long maxWaitMillis)方法
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
DruidPooledConnection poolableConnection;
while(true) {
while(true) {
try {
// 进行连接 maxWaitMillis就是我们指定的maxWait时间
poolableConnection = this.getConnectionInternal(maxWaitMillis);
break;
} catch (GetConnectionTimeoutException var23) {
if (notFullTimeoutRetryCnt > this.notFullTimeoutRetryCount || this.isFull()) {
throw var23;
}
++notFullTimeoutRetryCnt;
if (LOG.isWarnEnabled()) {
LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
}
}
}
有兴趣的可以debug看一下内部实现