个人随笔
目录
关于Druid重试机制的问题及解决方案
2020-06-12 22:40:39

使用了Druid作为数据库连接池,当数据源链接失败时,报出了以下错误:

  1. 019-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
  2. java.sql.SQLException: Access denied for user 'malluser'@'*.*.*.*' (using password: YES)
  3. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:545) ~[mysql-connector-java-6.0.6.jar:6.0.6]
  4. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513) ~[mysql-connector-java-6.0.6.jar:6.0.6]
  5. at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115) ~[mysql-connector-java-6.0.6.jar:6.0.6]
  6. ...

对于错误相信大家一看都明白是怎么回事,但是问题是Druid会一直不停的对数据源进行重试连接,这样的话我们的日志很快就爆了,为什么Druid会一直进行重试呢?首先让我们看一下源码:

  1. public class CreateConnectionTask implements Runnable {
  2. private int errorCount = 0;
  3. @Override
  4. public void run() {
  5. runInternal();
  6. }
  7. private void runInternal() {
  8. for (;;) {
  9. // addLast
  10. lock.lock();
  11. try {
  12. if (closed || closing) {
  13. createTaskCount--;
  14. return;
  15. }
  16. boolean emptyWait = true;
  17. if (createError != null && poolingCount == 0) {
  18. emptyWait = false;
  19. }
  20. if (emptyWait) {
  21. // 必须存在线程等待,才创建连接
  22. if (poolingCount >= notEmptyWaitThreadCount //
  23. && !(keepAlive && activeCount + poolingCount < minIdle)) {
  24. createTaskCount--;
  25. return;
  26. }
  27. // 防止创建超过maxActive数量的连接
  28. if (activeCount + poolingCount >= maxActive) {
  29. createTaskCount--;
  30. return;
  31. }
  32. }
  33. } finally {
  34. lock.unlock();
  35. }
  36. PhysicalConnectionInfo physicalConnection = null;
  37. try {
  38. physicalConnection = createPhysicalConnection();
  39. setFailContinuous(false);
  40. } catch (OutOfMemoryError e) {
  41. LOG.error("create connection OutOfMemoryError, out memory. ", e);
  42. errorCount++;
  43. if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
  44. // fail over retry attempts
  45. setFailContinuous(true);
  46. if (failFast) {
  47. lock.lock();
  48. try {
  49. notEmpty.signalAll();
  50. } finally {
  51. lock.unlock();
  52. }
  53. }
  54. if (breakAfterAcquireFailure) {
  55. lock.lock();
  56. try {
  57. createTaskCount--;
  58. } finally {
  59. lock.unlock();
  60. }
  61. return;
  62. }
  63. this.errorCount = 0; // reset errorCount
  64. if (closing || closed) {
  65. createTaskCount--;
  66. return;
  67. }
  68. createSchedulerFuture = createScheduler.schedule(this, timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS);
  69. return;
  70. }
  71. } catch (SQLException e) {
  72. LOG.error("create connection SQLException, url: " + jdbcUrl, e);
  73. errorCount++;
  74. if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
  75. // fail over retry attempts
  76. setFailContinuous(true);
  77. if (failFast) {
  78. lock.lock();
  79. try {
  80. notEmpty.signalAll();
  81. } finally {
  82. lock.unlock();
  83. }
  84. }
  85. if (breakAfterAcquireFailure) {
  86. lock.lock();
  87. try {
  88. createTaskCount--;
  89. } finally {
  90. lock.unlock();
  91. }
  92. return;
  93. }
  94. this.errorCount = 0; // reset errorCount
  95. if (closing || closed) {
  96. createTaskCount--;
  97. return;
  98. }
  99. createSchedulerFuture = createScheduler.schedule(this, timeBetweenConnectErrorMillis, TimeUnit.MILLISECONDS);
  100. return;
  101. }
  102. } catch (RuntimeException e) {
  103. LOG.error("create connection RuntimeException", e);
  104. // unknow fatal exception
  105. setFailContinuous(true);
  106. continue;
  107. } catch (Error e) {
  108. lock.lock();
  109. try {
  110. createTaskCount--;
  111. } finally {
  112. lock.unlock();
  113. }
  114. LOG.error("create connection Error", e);
  115. // unknow fatal exception
  116. setFailContinuous(true);
  117. break;
  118. } catch (Throwable e) {
  119. LOG.error("create connection unexecpted error.", e);
  120. break;
  121. }
  122. if (physicalConnection == null) {
  123. continue;
  124. }
  125. boolean result = put(physicalConnection);
  126. if (!result) {
  127. JdbcUtils.close(physicalConnection.getPhysicalConnection());
  128. LOG.info("put physical connection to pool failed.");
  129. }
  130. break;
  131. }
  132. }
  133. }
  134. public class CreateConnectionThread extends Thread {
  135. public CreateConnectionThread(String name){
  136. super(name);
  137. this.setDaemon(true);
  138. }
  139. public void run() {
  140. initedLatch.countDown();
  141. long lastDiscardCount = 0;
  142. int errorCount = 0;
  143. for (;;) {
  144. // addLast
  145. try {
  146. lock.lockInterruptibly();
  147. } catch (InterruptedException e2) {
  148. break;
  149. }
  150. long discardCount = DruidDataSource.this.discardCount;
  151. boolean discardChanged = discardCount - lastDiscardCount > 0;
  152. lastDiscardCount = discardCount;
  153. try {
  154. boolean emptyWait = true;
  155. if (createError != null
  156. && poolingCount == 0
  157. && !discardChanged) {
  158. emptyWait = false;
  159. }
  160. if (emptyWait
  161. && asyncInit && createCount.get() < initialSize) {
  162. emptyWait = false;
  163. }
  164. if (emptyWait) {
  165. // 必须存在线程等待,才创建连接
  166. if (poolingCount >= notEmptyWaitThreadCount //
  167. && !(keepAlive && activeCount + poolingCount < minIdle)) {
  168. empty.await();
  169. }
  170. // 防止创建超过maxActive数量的连接
  171. if (activeCount + poolingCount >= maxActive) {
  172. empty.await();
  173. continue;
  174. }
  175. }
  176. } catch (InterruptedException e) {
  177. lastCreateError = e;
  178. lastErrorTimeMillis = System.currentTimeMillis();
  179. if (!closing) {
  180. LOG.error("create connection Thread Interrupted, url: " + jdbcUrl, e);
  181. }
  182. break;
  183. } finally {
  184. lock.unlock();
  185. }
  186. PhysicalConnectionInfo connection = null;
  187. try {
  188. connection = createPhysicalConnection();
  189. setFailContinuous(false);
  190. } catch (SQLException e) {
  191. LOG.error("create connection SQLException, url: " + jdbcUrl + ", errorCode " + e.getErrorCode()
  192. + ", state " + e.getSQLState(), e);
  193. errorCount++;
  194. if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
  195. // fail over retry attempts
  196. setFailContinuous(true);
  197. if (failFast) {
  198. lock.lock();
  199. try {
  200. notEmpty.signalAll();
  201. } finally {
  202. lock.unlock();
  203. }
  204. }
  205. if (breakAfterAcquireFailure) {
  206. break;
  207. }
  208. try {
  209. Thread.sleep(timeBetweenConnectErrorMillis);
  210. } catch (InterruptedException interruptEx) {
  211. break;
  212. }
  213. }
  214. } catch (RuntimeException e) {
  215. LOG.error("create connection RuntimeException", e);
  216. setFailContinuous(true);
  217. continue;
  218. } catch (Error e) {
  219. LOG.error("create connection Error", e);
  220. setFailContinuous(true);
  221. break;
  222. }
  223. if (connection == null) {
  224. continue;
  225. }
  226. boolean result = put(connection);
  227. if (!result) {
  228. JdbcUtils.close(connection.getPhysicalConnection());
  229. LOG.info("put physical connection to pool failed.");
  230. }
  231. errorCount = 0; // reset errorCount
  232. }
  233. }
  234. }

从源码中我们可以看到,线程中使用了无参for循环再一直尝试进行数据源连接,代码中【errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0】当满足该判断条件时就会进行重试连接,接下来我们看一下源码中这两个属性值设置的是什么呢?(源码过长,只展示我们需要的代码,其他属性信息可自行扒源码~)

  1. private static final long serialVersionUID = 1L;
  2. public final static int DEFAULT_INITIAL_SIZE = 0;
  3. public final static int DEFAULT_MAX_ACTIVE_SIZE = 8;
  4. public final static int DEFAULT_MAX_IDLE= 8;
  5. public final static int DEFAULT_MIN_IDLE= 0;
  6. public final static int DEFAULT_MAX_WAIT = -1;
  7. public final static String DEFAULT_VALIDATION_QUERY =null;//
  8. public final static boolean DEFAULT_TEST_ON_BORROW = false;
  9. public final static boolean DEFAULT_TEST_ON_RETURN = false;
  10. public final static boolean DEFAULT_WHILE_IDLE= true;
  11. public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L;
  12. public static final long DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500;
  13. public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3;
  14. /*****************************华丽的分割线中间省略代码若干行*********************************/
  15. protected volatile long timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
  16. protected int connectionErrorRetryAttempts = 1;
  17. protected boolean breakAfterAcquireFailure = false;

从中这段代码中我们可以看到connectionErrorRetryAttempts值为1,timeBetweenConnectErrorMillis值为60000,而breakAfterAcquireFailure值为false,因此当我们数据源连接失败后,就会不断的进行重试连接,因此我对于对于该如何解决这样的问题我们就有了答案:

1.若不想让重试,我们可以设置breakAfterAcquireFailure(true);connectionErrorRetryAttempts(0);
2.若想要设置多久重试,我们只需要设置timeBetweenConnectErrorMillis(time);

action:经过亲测,直接在配置文件中配置属性并不能读取到(Druid设计时就这样,大神的思维暂时还不能参悟~),我们可直接将值写入程序当中,如下:

  1. private static void Init() {
  2. try {
  3. Properties properties = loadPropertiesFile("db.properties");
  4. druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties); // DruidDataSrouce工厂模式
  5. // TODO 调试配置,用完删除
  6. druidDataSource.setRemoveAbandoned(true);
  7. druidDataSource.setRemoveAbandonedTimeout(600);
  8. druidDataSource.setLogAbandoned(true);
  9. // druidDataSource.setBreakAfterAcquireFailure(true);
  10. druidDataSource.setTimeBetweenConnectErrorMillis(60000);
  11. // druidDataSource.setConnectionErrorRetryAttempts(0);
  12. } catch (Exception e) {
  13. logger.error(DbPoolConnection.class, e);
  14. }

当然若是想要方便以后修改,我们可以在properties文件中设置该值,读取出来配置文件的值再赋值给druidDataSource。

到这里,本文就结束了,如有不当之处,欢迎指正!

————————————————

版权声明:本文为CSDN博主「czzxueyang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sadness_lxy/java/article/details/89136418

 2031

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2