------------恢復內容開始------------
轉載地址:https://blog.csdn.net/sadness_lxy/article/details/89136418
在昨天項目開發中,使用了Druid作為數據庫連接池,當數據源密碼錯誤時,報出了以下錯誤:
2019-04-09 10:09:36 [Druid-ConnectionPool-Create-2053591126] [ com.alibaba.druid.pool.DruidDataSource ] [ 53 ] [ ERROR ] create connection SQLException, url: jdbc:mysql://*.*.*.*:3306/*?characterEncoding=utf-8&useSSL=false, errorCode 1045, state 28000
java.sql.SQLException: Access denied for user 'malluser'@'*.*.*.*' (using password: YES)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:545) ~[mysql-connector-java-6.0.6.jar:6.0.6]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513) ~[mysql-connector-java-6.0.6.jar:6.0.6]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115) ~[mysql-connector-java-6.0.6.jar:6.0.6]
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:1606) ~[mysql-connector-java-6.0.6.jar:6.0.6]
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:633) ~[mysql-connector-java-6.0.6.jar:6.0.6]
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:347) ~[mysql-connector-java-6.0.6.jar:6.0.6]
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:219) ~[mysql-connector-java-6.0.6.jar:6.0.6]
at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:149) ~[druid-1.1.6%20.jar:1.1.6]
at com.alibaba.druid.filter.stat.StatFilter.connection_connect(StatFilter.java:218) ~[druid-1.1.6%20.jar:1.1.6]
at com.alibaba.druid.filter.FilterChainImpl.connection_connect(FilterChainImpl.java:143) ~[druid-1.1.6%20.jar:1.1.6]
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1512) ~[druid-1.1.6%20.jar:1.1.6]
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1575) ~[druid-1.1.6%20.jar:1.1.6]
at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2450) [druid-1.1.6%20.jar:1.1.6]
對於錯誤相信大家一看都明白是怎么回事,但是問題是Druid會一直不停的對數據源進行重試連接,這樣的話我們的日志很快就爆了,為什么Druid會一直進行重試呢?首先讓我們看一下源碼:
public class CreateConnectionTask implements Runnable { private int errorCount = 0; @Override public void run() { runInternal(); } private void runInternal() { for (;;) { // addLast lock.lock(); try { if (closed || closing) { createTaskCount--; return; } boolean emptyWait = true; if (createError != null && poolingCount == 0) { emptyWait = false; } if (emptyWait) { // 必須存在線程等待,才創建連接 if (poolingCount >= notEmptyWaitThreadCount // && !(keepAlive && activeCount + poolingCount < minIdle)) { createTaskCount--; return; } // 防止創建超過maxActive數量的連接 if (activeCount + poolingCount >= maxActive) { createTaskCount--; return; } } } finally { lock.unlock(); } PhysicalConnectionInfo physicalConnection = null; try { physicalConnection = createPhysicalConnection(); setFailContinuous(false); } catch (OutOfMemoryError e) { LOG.error("create connection OutOfMemoryError, out memory. ", e); errorCount++; if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) { // fail over retry attempts setFailContinuous(true); if (failFast) { lock.lock(); try { notEmpty.signalAll(); } finally { lock.unlock(); } } if (breakAfterAcquireFailure) { lock.lock(); try { createTaskCount--; } finally { lock.unlock(); } return; } this.errorCount = 0; // reset errorCount if (closing || closed) { createTaskCount--; return; } createSchedulerFuture = createScheduler.schedule(this, timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS); return; } } catch (SQLException e) { LOG.error("create connection SQLException, url: " + jdbcUrl, e); errorCount++; if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) { // fail over retry attempts setFailContinuous(true); if (failFast) { lock.lock(); try { notEmpty.signalAll(); } finally { lock.unlock(); } } if (breakAfterAcquireFailure) { lock.lock(); try { createTaskCount--; } finally { lock.unlock(); } return; } this.errorCount = 0; // reset errorCount if (closing || closed) { createTaskCount--; return; } createSchedulerFuture = createScheduler.schedule(this, timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS); return; } } catch (RuntimeException e) { LOG.error("create connection RuntimeException", e); // unknow fatal exception setFailContinuous(true); continue; } catch (Error e) { lock.lock(); try { createTaskCount--; } finally { lock.unlock(); } LOG.error("create connection Error", e); // unknow fatal exception setFailContinuous(true); break; } catch (Throwable e) { LOG.error("create connection unexecpted error.", e); break; } if (physicalConnection == null) { continue; } boolean result = put(physicalConnection); if (!result) { JdbcUtils.close(physicalConnection.getPhysicalConnection()); LOG.info("put physical connection to pool failed."); } break; } } } public class CreateConnectionThread extends Thread { public CreateConnectionThread(String name){ super(name); this.setDaemon(true); } public void run() { initedLatch.countDown(); long lastDiscardCount = 0; int errorCount = 0; for (;;) { // addLast try { lock.lockInterruptibly(); } catch (InterruptedException e2) { break; } long discardCount = DruidDataSource.this.discardCount; boolean discardChanged = discardCount - lastDiscardCount > 0; lastDiscardCount = discardCount; try { boolean emptyWait = true; if (createError != null && poolingCount == 0 && !discardChanged) { emptyWait = false; } if (emptyWait && asyncInit && createCount.get() < initialSize) { emptyWait = false; } if (emptyWait) { // 必須存在線程等待,才創建連接 if (poolingCount >= notEmptyWaitThreadCount // && !(keepAlive && activeCount + poolingCount < minIdle)) { empty.await(); } // 防止創建超過maxActive數量的連接 if (activeCount + poolingCount >= maxActive) { empty.await(); continue; } } } catch (InterruptedException e) { lastCreateError = e; lastErrorTimeMillis = System.currentTimeMillis(); if (!closing) { LOG.error("create connection Thread Interrupted, url: " + jdbcUrl, e); } break; } finally { lock.unlock(); } PhysicalConnectionInfo connection = null; try { connection = createPhysicalConnection(); setFailContinuous(false); } catch (SQLException e) { LOG.error("create connection SQLException, url: " + jdbcUrl + ", errorCode " + e.getErrorCode() + ", state " + e.getSQLState(), e); errorCount++; if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) { // fail over retry attempts setFailContinuous(true); if (failFast) { lock.lock(); try { notEmpty.signalAll(); } finally { lock.unlock(); } } if (breakAfterAcquireFailure) { break; } try { Thread.sleep(timeBetweenConnectErrorMillis); } catch (InterruptedException interruptEx) { break; } } } catch (RuntimeException e) { LOG.error("create connection RuntimeException", e); setFailContinuous(true); continue; } catch (Error e) { LOG.error("create connection Error", e); setFailContinuous(true); break; } if (connection == null) { continue; } boolean result = put(connection); if (!result) { JdbcUtils.close(connection.getPhysicalConnection()); LOG.info("put physical connection to pool failed."); } errorCount = 0; // reset errorCount } } }
從源碼中我們可以看到,線程中使用了無參for循環再一直嘗試進行數據源連接,代碼中【errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0】當滿足該判斷條件時就會進行重試連接,接下來我們看一下源碼中這兩個屬性值設置的是什么呢?(源碼過長,只展示我們需要的代碼,其他屬性信息可自行扒源碼~)
private static final long serialVersionUID = 1L;
public final static int DEFAULT_INITIAL_SIZE = 0;
public final static int DEFAULT_MAX_ACTIVE_SIZE = 8;
public final static int DEFAULT_MAX_IDLE = 8;
public final static int DEFAULT_MIN_IDLE = 0;
public final static int DEFAULT_MAX_WAIT = -1;
public final static String DEFAULT_VALIDATION_QUERY = null; //
public final static boolean DEFAULT_TEST_ON_BORROW = false;
public final static boolean DEFAULT_TEST_ON_RETURN = false;
public final static boolean DEFAULT_WHILE_IDLE = true;
public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L;
public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500;
public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3;
/*****************************華麗的分割線中間省略代碼若干行*********************************/
protected volatile long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
protected int connectionErrorRetryAttempts = 1;
protected boolean breakAfterAcquireFailure = false;
從中這段代碼中我們可以看到connectionErrorRetryAttempts值為1,timeBetweenConnectErrorMillis值為60000,而breakAfterAcquireFailure值為false,因此當我們數據源連接失敗后,就會不斷的進行重試連接,因此我對於對於該如何解決這樣的問題我們就有了答案:
1.若不想讓重試,我們可以設置breakAfterAcquireFailure(true);connectionErrorRetryAttempts(0);
2.若想要設置多久重試,我們只需要設置timeBetweenConnectErrorMillis(time);
action:經過親測,直接在配置文件中配置屬性並不能讀取到(Druid設計時就這樣,大神的思維暫時還不能參悟~),我們可直接將值寫入程序當中,如下:
private static void Init() { try { Properties properties = loadPropertiesFile("db.properties"); druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties); // DruidDataSrouce工廠模式 // TODO 調試配置,用完刪除 druidDataSource.setRemoveAbandoned(true); druidDataSource.setRemoveAbandonedTimeout(600); druidDataSource.setLogAbandoned(true); // druidDataSource.setBreakAfterAcquireFailure(true); druidDataSource.setTimeBetweenConnectErrorMillis(60000); // druidDataSource.setConnectionErrorRetryAttempts(0); } catch (Exception e) { logger.error(DbPoolConnection.class, e); } }
當然若是想要方便以后修改,我們可以在properties文件中設置該值,讀取出來配置文件的值再賦值給druidDataSource。
到這里,本文就結束了,如有不當之處,歡迎指正!