qidao123.com技术社区-IT企服评测·应用市场

标题: 深入剖析 Druid 连接池:连接有用性检测与 Keep-Alive 机制 [打印本页]

作者: 东湖之滨    时间: 2025-3-17 10:15
标题: 深入剖析 Druid 连接池:连接有用性检测与 Keep-Alive 机制
背景

在 Java 程序中,下面是一个经常会遇到的错误。
  1. Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure

  2. The last packet successfully received from the server was 30,027 milliseconds ago. The last packet sent successfully to the server was 30,028 milliseconds ago.
复制代码
该错误通常是由于 MySQL 连接意外断开导致的,常见原因包括:
本文将深入剖析 Druid 连接池的连接有用性检测机制,重点探讨以下内容:
盼望通过本篇分析,帮助各人更深入理解 Druid 连接池的运行机制。
什么场景下会检测连接的有用性

Druid 连接池在以下四种场景下会检测连接的有用性:
下面我们看看这四种场景的具体实现逻辑。
1. 申请连接

当应用从连接池申请空闲连接时,会检查连接的有用性,与之相关的参数有两个:testOnBorrow 和 testWhileIdle。
申请连接是在getConnectionDirect方法中实现的,下面我们看看该方法的具体实现细节。
  1. public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
  2.         int notFullTimeoutRetryCnt = 0;
  3.         for (; ; ) {
  4.             DruidPooledConnection poolableConnection;
  5.             try {
  6.                 // 从连接池中获取空闲连接
  7.                 poolableConnection = getConnectionInternal(maxWaitMillis);
  8.             } catch (GetConnectionTimeoutException ex) {
  9.                 ...
  10.             }
  11.             // 如果testOnBorrow为true,则会调用testConnectionInternal检测连接的有效性
  12.             if (testOnBorrow) {
  13.                 boolean validated = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
  14.                 if (!validated) {
  15.                     if (LOG.isDebugEnabled()) {
  16.                         LOG.debug("skip not validated connection.");
  17.                     }
  18.                     // 如果连接无效,则会调用discardConnection丢弃该连接,并继续从连接池中获取新的空闲连接。
  19.                     discardConnection(poolableConnection.holder);
  20.                     continue;
  21.                 }
  22.             } else {
  23.                 ...
  24.                 // 如果testOnBorrow不为true,且testWhileIdle为true,则判断连接的空闲时间是否超过timeBetweenEvictionRunsMillis,如果超过,也会调用testConnectionInternal检测连接的有效性
  25.                 if (testWhileIdle) {
  26.                     final DruidConnectionHolder holder = poolableConnection.holder;
  27.                     long currentTimeMillis = System.currentTimeMillis();
  28.                     long lastActiveTimeMillis = holder.lastActiveTimeMillis;
  29.                     ...
  30.                     long idleMillis = currentTimeMillis - lastActiveTimeMillis;

  31.                     if (idleMillis >= timeBetweenEvictionRunsMillis
  32.                             || idleMillis < 0 // unexcepted branch
  33.                     ) {
  34.                         boolean validated = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
  35.                         if (!validated) {
  36.                             if (LOG.isDebugEnabled()) {
  37.                                 LOG.debug("skip not validated connection.");
  38.                             }
  39.                             // 如果连接无效,则会调用discardConnection丢弃该连接,并继续从连接池中获取新的空闲连接。
  40.                             discardConnection(poolableConnection.holder);
  41.                             continue;
  42.                         }
  43.                     }
  44.                 }
  45.             }
  46.             ...
  47.             return poolableConnection;
  48.         }
  49.     }
复制代码
该方法的处理流程如下:
以是,Druid 连接池默认情况下,每 60 秒(由 timeBetweenEvictionRunsMillis 参数控制)执行一次连接回收和维护操作,并保持肯定数目的空闲连接。其核心逻辑包括:
必要注意的是,即使连接开启了定期探活检测,若发生超时,仍会被回收
接下来,我们看看上述参数的默认值:
为什么设置的 validationQuery 没有用果?

在 Druid 连接池中,判定连接是否有用时,通常调用 testConnectionInternal 或 validateConnection 方法。这两个方法的核心逻辑根本雷同,具体如下:
以下是 MySQL 实现类(MySqlValidConnectionChecker)中isValidConnection方法的具体实现。
  1. // DruidDataSource.java
  2. protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
  3.         final DruidConnectionHolder holder = pooledConnection.holder;
  4.             ...
  5.             if (testOnReturn) {
  6.                 boolean validated = testConnectionInternal(holder, physicalConnection);
  7.                 if (!validated) {
  8.                     JdbcUtils.close(physicalConnection);
  9.                     ...
  10.                 }
  11.             }
  12.             ...
  13.     }
复制代码
方法中的 usePingMethod 受druid.mysql.usePingMethod参数控制,其默认值为 true。
当 usePingMethod 等于 true 时,validateQuery 将被设置为 DEFAULT_VALIDATION_QUERY,即/* ping */ SELECT 1,而非用户自界说的 validationQuery。
execValidQuery() 方法执行 validateQuery 时,如果查询语句以/* ping */开头,MySQL JDBC 驱动会进行特殊处理。
具体来说,MySQL JDBC 在剖析 SQL 语句时,会判定它是否以 PING_MARKER(即/* ping */)开头,如果是,则不会执行 SQL 语句,而是调用doPingInstead(),直接向 MySQL 服务器发送 COM_PING  命令,这样可以减少 SQL 剖析和执行的开销,提高性能。
  1. public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
  2.         String url = this.getUrl();
  3.         Properties connectProperties = getConnectProperties();
  4.         ...
  5.         try {
  6.             // 这里会调用驱动的 connect 方法来建立连接
  7.             conn = createPhysicalConnection(url, physicalConnectProperties);
  8.             connectedNanos = System.nanoTime();

  9.             if (conn == null) {
  10.                 throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);
  11.             }
  12.             ...
  13.             if (!initSqls(conn, variables, globalVariables)) {
  14.                 validateConnection(conn);
  15.             }
  16.             ...
  17.         } 
  18.         ...
  19.         return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos, variables, globalVariables);
  20.     }
复制代码
关于参数设置的几点建议

总结


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/) Powered by Discuz! X3.4