解决HikariCP连接池提示TDengine ERROR (8000000b)问题
    
    
    
    一、异常堆栈日志
   
[xxx-xxx-xxx/v1.0.0 - 192.168.0.9:8080]2022-10-0802:00:09.086WARN 1[][http-nio-6666-exec-8]
com.zaxxer.hikari.pool.PoolBaseTDengineHikariCP - Failed to validate connection com.taosdata.jdbc.TSDBConnection@13788ad1 
(TDengine ERROR (8000000b): Unable to establish connection). Possibly consider using ashorter maxLifetime value.
    
    
    二、查看HikariCP源码
   
    
    
    2.1 CodahaleHealthChecker类
   
    定位到
    
     CodahaleHealthChecker
    
    类确认异常发生位置,在
    
     check函数
    
    中执行健康检查
    
     pool.getConnection(checkTimeoutMs)
    
public final class CodahaleHealthChecker{
...
...
...
      @Override
      protected Result check() throws Exception
      {
         try (Connection connection = pool.getConnection(checkTimeoutMs)) {
            return Result.healthy();
         }
         catch (SQLException e) {
            return Result.unhealthy(e);
         }
      }
...
...
...
}
    
    
    2.2 HikariPool类
   
    定位到
    
     getConnection函数
    
    ,看到是属于
    
     HikariPool类
    
    的函数,其内部实现了
    
     isConnectionAlive(poolEntry.connection)
    
    的判断
   
public final class HikariPool extends PoolBase implements HikariPoolMXBean, IBagStateListener
{
...
...
...
   public Connection getConnection(final long hardTimeout) throws SQLException
   {
      suspendResumeLock.acquire();
      final long startTime = currentTime();
      try {
         long timeout = hardTimeout;
         do {
            PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
            if (poolEntry == null) {
               break; // We timed out... break and throw exception
            }
            final long now = currentTime();
            if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {
               closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
               timeout = hardTimeout - elapsedMillis(startTime);
            }
            else {
               metricsTracker.recordBorrowStats(poolEntry, startTime);
               return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
            }
         } while (timeout > 0L);
         metricsTracker.recordBorrowTimeoutStats(startTime);
         throw createTimeoutException(startTime);
      }
      catch (InterruptedException e) {
         Thread.currentThread().interrupt();
         throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
      }
      finally {
         suspendResumeLock.release();
      }
   }
...
...
...
}
    
    
    2.3 分析静态代码块
   
    从
    
     isConnectionAlive(poolEntry.connection)
    
    的实现看到,大概是因为调用
    
     connection.createStatement()
    
    发生异常触发了
    
     catch代码块
    
    的执行,抛出:
    
     (TDengine ERROR (8000000b): Unable to establish connection). Possibly consider using ashorter maxLifetime value.
    
   boolean isConnectionAlive(final Connection connection)
   {
      try {
         try {
            setNetworkTimeout(connection, validationTimeout);
            final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
            if (isUseJdbc4Validation) {
               return connection.isValid(validationSeconds);
            }
            try (Statement statement = connection.createStatement()) {
               if (isNetworkTimeoutSupported != TRUE) {
                  setQueryTimeout(statement, validationSeconds);
               }
               statement.execute(config.getConnectionTestQuery());
            }
         }
         finally {
            setNetworkTimeout(connection, networkTimeout);
            if (isIsolateInternalQueries && !isAutoCommit) {
               connection.rollback();
            }
         }
         return true;
      }
      catch (Exception e) {
         lastConnectionFailure.set(e);
         logger.warn("{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.",
                     poolName, connection, e.getMessage());
         return false;
      }
   }
    
    
    三、翻阅TDengine官方文档
   
    
    
    3.1 参考TDengine与连接池HikariCP的使用[^1]
   
     
   
    
    
    3.2 查看应用的yaml配置
   
    目前
    
     max-lifetime
    
    设置了1800000(单位ms)即30min,此时会抛出Failed to validate connection的异常
   
#数据源配置
spring:
  datasource:
    tdengine:
      driver-class-name: com.taosdata.jdbc.TSDBDriver
      jdbc-url: jdbc:TAOS://xxx-hostname:6030/xxx?enableMicrosecond=true&charset=UTF-8&locale=en_US.UTF-8
      username: xxx
      password: xxx
      ...
      ...
      ...
      #连接池名
      pool-name: TDengineHikariCP
      #最大生命周期
      max-lifetime: 1800000
      #连接超时时间
      connection-timeout: 30000
      connection-test-query: select 1 
    
    
    四、更新max-lifetime
   
    按TDengine官方示例,将
    
     max-lifetime
    
    设置为0,表示最大生命周期。这里
    
     正确的做法
    
    应该是逐步调小max-lifetime的数值,直到不再抛出异常,参考Github HikariCP issues[^2]
   
#数据源配置
spring:
  datasource:
    tdengine:
      ...
      #最大生命周期
      max-lifetime: 0
      ...
    
    
    五、观察应用运行状态
   
    检查是否仍抛出异常
    
     (TDengine ERROR (8000000b): Unable to establish connection). Possibly consider using ashorter maxLifetime value.
    
    参考内容:
    
    [1]:
    
     TDengine与连接池HikariCP的使用 https://docs.taosdata.com/2.4/reference/connector/java/#hikaricp
    
    
    [2]:
    
     逐步调小max-lifetime参数值 https://github.com/brettwooldridge/HikariCP/issues/1651
    
   
 
