前言
从这一章开始进入HikariCP的核心源码,本章学习HikariCP获取与创建连接的流程。
![31f68692a4a75986bd94a19fc2e6b64f.png](https://i-blog.csdnimg.cn/blog_migrate/50c85a3688c457448ea53f7193ac055c.png)
一、代理
![6239949f75f8d07ba116533e8adf2873.png](https://i-blog.csdnimg.cn/blog_migrate/f4edbd8ad7f41f882989d8cbdafe5fa0.png)
Hikari返回给用户
Connection
、
ResultSet
等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](https://i-blog.csdnimg.cn/blog_migrate/c3c2f281d8f74f8ef22a83df34acea98.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](https://i-blog.csdnimg.cn/blog_migrate/540a703e226c360fd4fa7898676af745.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
提供给用户。
当getConnection
从ConcurrentBag
中获取PoolEntry
失败,执行HikariPool#addBagItem
提交PoolEntryCreator
到线程池异步创建PoolEntry
放入ConcurrentBag
。