jsch连接ftp异常 connection rest_HikariCP源码阅读(四)获取与创建连接

67aedf3dae2bf15143e0a74ca3aee930.png

前言

从这一章开始进入HikariCP的核心源码,本章学习HikariCP获取与创建连接的流程。

31f68692a4a75986bd94a19fc2e6b64f.png

一、代理

6239949f75f8d07ba116533e8adf2873.png

Hikari返回给用户 ConnectionResultSet等java.sql对象实例,都是由 ProxyFactory创建的代理对象。如 Connection的代理对象是 HikariProxyConnection。而这些代理的class文件都是由Javassist通过字节码生成的,生成的逻辑见 JavassistProxyFactory
public static void main(String... args) throws Exception {
     classPool = new ClassPool();
     classPool.importPackage("java.sql");
     classPool.appendClassPath(new LoaderClassPath(JavassistProxyFactory.class.getClassLoader()));
     if (args.length > 0) {
         // 生成class文件存放位置,默认./target/classes
         genDirectory = args[0];
     }
     // 生成Connection、Statement、ResultSet、DatabaseMetaData代理类
     String methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { throw checkException(e); } }";
     generateProxyClass(Connection.class, ProxyConnection.class.getName(), methodBody);
     generateProxyClass(Statement.class, ProxyStatement.class.getName(), methodBody);
     generateProxyClass(ResultSet.class, ProxyResultSet.class.getName(), methodBody);
     generateProxyClass(DatabaseMetaData.class, ProxyDatabaseMetaData.class.getName(), methodBody);

     // 生成PreparedStatement、CallableStatement代理类
     methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }";
     generateProxyClass(PreparedStatement.class, ProxyPreparedStatement.class.getName(), methodBody);
     generateProxyClass(CallableStatement.class, ProxyCallableStatement.class.getName(), methodBody);

     // 修改ProxyFactory的实现
     modifyProxyFactory();
 }

 ```
 `ProxyFactory`的源码并没有直接生成代理对象,它的实现也是由Javassist生成的。
 ```
 public final class ProxyFactory {
   static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit)
   {throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
   }
 ```
 `JavassistProxyFactory#modifyProxyFactory`修改`ProxyFactory`实现。
 ```
 private static void modifyProxyFactory() throws NotFoundException, CannotCompileException, IOException {String packageName = ProxyConnection.class.getPackage().getName();
  CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.pool.ProxyFactory");for (CtMethod method : proxyCt.getMethods()) {switch (method.getName()) {case "getProxyConnection":// 调用HikariProxyConnection的构造方法
           method.setBody("{return new " + packageName + ".HikariProxyConnection($$);}");break;case "getProxyStatement":// 调用HikariProxyStatement的构造方法
           method.setBody("{return new " + packageName + ".HikariProxyStatement($$);}");break;case ...default:break;
     }
  }
  proxyCt.writeFile(genDirectory + "target/classes");
}

二、获取连接

fbc3be9cf7f713b898eb6b05f9df5cef.png

HikariPool#getConnection是获取连接的入口,超时时间是配置的connectionTimeout,返回对象是HikariProxyConnection。

public Connection getConnection(final long hardTimeout) throws SQLException {
   // 获取信号量
   suspendResumeLock.acquire();
   final long startTime = currentTime();
   try {
      long timeout = hardTimeout;
      do {
         // 获取空闲PoolEntry
         PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
         if (poolEntry == null) {
            // borrow超时返回空,结束循环
            break;
         }
         final long now = currentTime();
         // poolEntry被驱逐 或 非存活状态
         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 {
            // 创建连接代理
            return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
         }
      } while (timeout > 0L);
      // 超时抛出异常
      throw createTimeoutException(startTime);
   } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
   } finally {
      // 释放信号量
      suspendResumeLock.release();
   }
}

suspendResumeLock.acquire()

如果配置isAllowPoolSuspension=true(默认false),这里suspendResumeLock实例是SuspendResumeLock。如果连接池挂起,这里拿不到信号量,将会阻塞等待,直到连接池恢复到正常状态。

public class SuspendResumeLock {
   // 信号量
   private final Semaphore acquisitionSemaphore;
   public void acquire() throws SQLException {
      // 先尝试获取信号量
      if (acquisitionSemaphore.tryAcquire()) {
         return;
      }
      // 尝试获取信号量失败,com.zaxxer.hikari.throwIfSuspended如果为true,直接抛出异常
      else if (Boolean.getBoolean("com.zaxxer.hikari.throwIfSuspended")) {
         throw new SQLTransientException("The pool is currently suspended and configured to throw exceptions upon acquisition");
      }
      // 尝试获取信号量失败,这里阻塞等待获取到新的许可
      acquisitionSemaphore.acquireUninterruptibly();
   }
}

如果配置isAllowPoolSuspension=false,这里suspendResumeLock实例是SuspendResumeLock#FAUX_LOCK,获取信号量是空实现,作者希望通过JIToptimized away优化。

public static final SuspendResumeLock FAUX_LOCK = new SuspendResumeLock(false) {
   @Override
   public void acquire() {}
   @Override
   public void release() {}
   @Override
   public void suspend() {}
   @Override
   public void resume() {}
};

connectionBag.borrow(timeout, MILLISECONDS)

从connectionBag获取空闲连接,如果返回空,表示超时,直接结束循环抛出异常。ConcurrentBag在第二章讲过。

public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {
   // 1. 先从threadList ThreadLocal获取
   final List list = threadList.get();for (int i = list.size() - 1; i >= 0; i--) {final Object entry = list.remove(i);final T bagEntry = weakThreadLocals ? ((WeakReference) entry).get() : (T) entry;if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {return bagEntry;
      }
   }// 2. 再从shareList公共区域获取final int waiting = waiters.incrementAndGet();try {for (T bagEntry : sharedList) {if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {if (waiting > 1) {
               listener.addBagItem(waiting - 1);
            }return bagEntry;
         }
      }// 可能通知HikariPool添加Entry
      listener.addBagItem(waiting);// 3. 从handoffQueue交接队列等待获取
      timeout = timeUnit.toNanos(timeout);do {final long start = currentTime();final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {return bagEntry;
         }
         timeout -= elapsedNanos(start);
      } while (timeout > 10_000);return null;
   } finally {
      waiters.decrementAndGet();
   }
}

poolEntry.isMarkedEvicted()

检查连接entry是否被驱逐。只有在调用HikariPool#softEvictConnection方法后,会执行驱逐。

private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner) {
   // 标记被驱逐
   poolEntry.markEvicted();
   // 如果owner=true(用户发起软驱逐)或保留entry成功
   if (owner || connectionBag.reserve(poolEntry)) {
      // 关闭连接
      closeConnection(poolEntry, reason);
      return true;
   }
   return false;
}

有下列几种情况会调用softEvictConnection方法:

  • HikariPool#evictConnection用户主动调用驱逐连接。

  • HouseKeeper检测到时钟倒推,关闭所有连接。

  • HikariDataSource#close关闭连接池。

  • 连接超过MaxLifetime自动关闭。

elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection)

首先判断上次entry被使用的时间距离现在是否超过com.zaxxer.hikari.aliveBypassWindowMs默认500ms。

如果超过500ms需要进行链接存活检查,反过来说,假如连接频繁获取和归还,不用进行存活检查。

abstract class PoolBase {
   // 从HikariConfig而来,默认5000ms
   long validationTimeout;
   boolean isConnectionAlive(final Connection connection) {
      try {
         try {
            // 设置网络超时时间为5000ms
            setNetworkTimeout(connection, validationTimeout);
            // 单位转换为5s
            final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;
            // 如果connectionTestQuery配置为空,使用Connection自带的isValid方法检测
            // 对于com.mysql.cj.jdbc.ConnectionImpl#isValid就是通过ping的方式
            if (isUseJdbc4Validation) {
               return connection.isValid(validationSeconds);
            }
            // 如果connectionTestQuery配置不为空,执行配置的sql
            try (Statement statement = connection.createStatement()) {
               if (isNetworkTimeoutSupported != TRUE) {
                  setQueryTimeout(statement, validationSeconds);
               }
               statement.execute(config.getConnectionTestQuery());
            }
         } finally {
            // 恢复网络超时时间networkTimeout默认等于validationTimeout
            setNetworkTimeout(connection, networkTimeout);
            if (isIsolateInternalQueries && !isAutoCommit) {
               connection.rollback();
            }
         }
         return true;
      } catch (Exception e) {
         lastConnectionFailure.set(e);
         return false;
      }
   }
}

leakTaskFactory.schedule(poolEntry)

对于schedule方法如果配置leakDetectionThreshold,会按照leakDetectionThreshold定时执行ProxyLeakTask。否则返回ProxyLeakTask.NO_LEAK,是个空实现。

class ProxyLeakTaskFactory {
   private ScheduledExecutorService executorService;
   // 配置leakDetectionThreshold
   private long leakDetectionThreshold;

   ProxyLeakTaskFactory(final long leakDetectionThreshold, final ScheduledExecutorService executorService) {
      this.executorService = executorService;
      this.leakDetectionThreshold = leakDetectionThreshold;
   }

   ProxyLeakTask schedule(final PoolEntry poolEntry) {
      return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
   }

   void updateLeakDetectionThreshold(final long leakDetectionThreshold) {
      this.leakDetectionThreshold = leakDetectionThreshold;
   }

   private ProxyLeakTask scheduleNewTask(PoolEntry poolEntry) {
      ProxyLeakTask task = new ProxyLeakTask(poolEntry);
      task.schedule(executorService, leakDetectionThreshold);
      return task;
   }
}

连接泄露检测定时任务,逻辑见ProxyLeakTask#run,仅仅打印了一条日志。

class ProxyLeakTask implements Runnable {
   static final ProxyLeakTask NO_LEAK;
   private ScheduledFuture> scheduledFuture;
   private String connectionName;
   private Exception exception;
   private String threadName; 
   private boolean isLeaked;
   @Override
   public void run() {
      isLeaked = true;
      final StackTraceElement[] stackTrace = exception.getStackTrace(); 
      final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
      System.arraycopy(stackTrace, 5, trace, 0, trace.length);
      exception.setStackTrace(trace);
      // 打印日志
      LOGGER.warn("Connection leak detection triggered for {} on thread {}, stack trace follows", connectionName, threadName, exception);
   }
}

poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now)

getConnection最后调用javassist生成的ProxyFactory获取HikariProxyConnection

Connection createProxyConnection(final ProxyLeakTask leakTask, final long now) {
   return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
}

ProxyFactory只是调用了HikariProxyConnection的构造方法。

public final class ProxyFactory {
 static ProxyConnection getProxyConnection(PoolEntry var0, Connection var1, FastList var2, ProxyLeakTask var3, long var4, boolean var6, boolean var7) {
     return new HikariProxyConnection(var0, var1, var2, var3, var4, var6, var7);
 }
}

三、创建连接

getConnection中并没有看到获取实际Connection的逻辑,只是将PoolEntry中的Connection封装为ProxyConnection。其实创建Connection的触发点在两个地方,一个是HikariPool#addBagItem,一个是HouseKeeper定时任务。这一章先看看HikariPool#addBagItem

459bfd50fac5dbd40b474d10bb504363.png

HikariPool#getConnection的过程中,如果 ConcurrentBag从ThreadLocal中获取PoolEntry失败,则有可能触发 IBagStateListener#addBagItem。这里触发的正是 HikariPool#addBagItem,它会创建实际数据库连接,将实际Connection封装到PoolEntry中,再将PoolEntry放入ConcurrentBag中。

HikariPool#addBagItem

private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(null);
private final Collection addConnectionQueueReadOnlyView;private final ThreadPoolExecutor addConnectionExecutor;public HikariPool(final HikariConfig config) {super(config);this.connectionBag = new ConcurrentBag<>(this);final int maxPoolSize = config.getMaximumPoolSize();
   LinkedBlockingQueue addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize);this.addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue);this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());
}@Overridepublic void addBagItem(final int waiting) {final boolean shouldAdd = waiting - addConnectionQueueReadOnlyView.size() >= 0;if (shouldAdd) {
      addConnectionExecutor.submit(poolEntryCreator);
   }
}

waiting是由ConcurrentBag传入的,代表当前正在等待获取PoolEntry的线程数(一个非精确数,因为等待线程的数量时刻在变化)。

addConnectionExecutor是负责创建连接的线程池,配置参数之前讲过。核心线程数1,最大线程数1,5秒闲置时间(设置了允许核心线程回收),等待队列长度maxPoolSize,拒绝策略丢弃最老任务。

addConnectionQueueReadOnlyView在HikariPool构造的时候初始化,代表addConnectionExecutor的等待队列视图。

poolEntryCreator是一个创建PoolEntry的Callable任务。

addBagItem首先判断 等待获取连接的线程数 是否大于等于 addConnectionExecutor等待队列中的排队数,如果满足条件提交PoolEntryCreator任务到线程池执行创建PoolEntry。

PoolEntryCreator

创建PoolEntry的Callable。管控连接池是否需要添加连接,一次最多只会创建一个PoolEntry。

private final class PoolEntryCreator implements Callable<Boolean> {
   private final String loggingPrefix;

   PoolEntryCreator(String loggingPrefix) {
      this.loggingPrefix = loggingPrefix;
   }

   @Override
   public Boolean call() {
      long sleepBackoff = 250L;
      // 判断连接池是否需要添加连接
      while (poolState == POOL_NORMAL && shouldCreateAnotherConnection()) {
         // 创建Connection、创建PoolEntry
         final PoolEntry poolEntry = createPoolEntry();
         if (poolEntry != null) {
             // 放入Bag
            connectionBag.add(poolEntry);
            // 创建成功即返回,最多只会创建一个PoolEntry
            return Boolean.TRUE;
         }
         // 获取连接失败睡眠,最大不会超过10秒
         quietlySleep(sleepBackoff);
         sleepBackoff = Math.min(SECONDS.toMillis(10), Math.min(connectionTimeout, (long) (sleepBackoff * 1.5)));
      }
      return Boolean.FALSE;
   }

   private synchronized boolean shouldCreateAnotherConnection() {
      return getTotalConnections()          (connectionBag.getWaitingThreadCount() > 0 || getIdleConnections()    }
   @Override
   public int getTotalConnections() {
      // sharedList.size()
      return connectionBag.size();
   }
}

shouldCreateAnotherConnection判断是否需要添加连接。getTotalConnections() < config.getMaximumPoolSize()是前提条件,当前总连接数即ConcurrentBag中shareList里的PoolEntry数量,必须小于配置的MaxPoolSize。接下来还需满足任意一个条件:

  • connectionBag.getWaitingThreadCount() > 0:ConcurrentBag中waiters.get()返回等待线程数量大于0。表示真的有线程需要获取连接,正在等待从shareList或handoffQueue获取PoolEntry。

  • getIdleConnections() < config.getMinimumIdle():ConcurrentBag中状态为STATE_NOT_IN_USE的PoolEntry数量小于配置minimumIdle最小连接数。


HikariPool#createPoolEntry方法创建PoolEntry。连接超过maxLifetime会关闭并通知HikariPool#addItem添加连接。

private PoolEntry createPoolEntry() {
   try {
      // 创建PoolEntry
      final PoolEntry poolEntry = newPoolEntry();
      final long maxLifetime = config.getMaxLifetime();
      if (maxLifetime > 0) {
         // 计算lifetime 会在maxLifetime的基础上减去一个随机数,防止同一时间大量连接被关闭
         final long variance = maxLifetime > 10000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
         final long lifetime = maxLifetime - variance;
         // 连接超过MaxLifeTime后,重新创建连接
         poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
            () -> {
               // 软驱逐,前面讲过,如果entry的状态改为reserve成功,这里会关闭连接
               if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false)) {
                  // 通知HikariPool增加元素
                  addBagItem(connectionBag.getWaitingThreadCount());
               }
            },
            lifetime, MILLISECONDS));
      }
      return poolEntry;
   } catch (ConnectionSetupException e) {
      if (poolState == POOL_NORMAL) {
         lastConnectionFailure.set(e);
      }
   } catch (Exception e) {
   }
   // 如果有异常,返回空
   return null;
}

PoolBase#newPoolEntry操作DriverDataSource获取真正的Connection

PoolEntry newPoolEntry() throws Exception {
   return new PoolEntry(newConnection(), this, isReadOnly, isAutoCommit);
}
private Connection newConnection() throws Exception {
   Connection connection = null;
   try {
      // DriverDataSource获取Connection
      String username = config.getUsername();
      String password = config.getPassword();
      connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
      if (connection == null) {
         throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
      }
      // 初始化连接的一些参数,比如readOnly,autoCommit
      setupConnection(connection);
      lastConnectionFailure.set(null);
      return connection;
   } catch (Exception e) {
      throw e;
   }
}

总结

getConnection获取连接方法是将PoolEntry中的Connection实例包装为代理对象HikariProxyConnection提供给用户。

getConnectionConcurrentBag中获取PoolEntry失败,执行HikariPool#addBagItem提交PoolEntryCreator到线程池异步创建PoolEntry放入ConcurrentBag

c948f65a5f779563cc251856ba986b4a.png

f16440ea529a5e639cc3d7dbc919d079.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值