获取连接的过程
HikariDataSource 提供获取数据库连接的方法:getConnection(),但真正是从连接池中获取连接的,即:HikariPool#getConnection(). 获取连接的时序图如下:
对应的源码内容如下:
public Connection getConnection() throws SQLException
{
// 检查数据源是否已经关闭,如果关闭了则抛出异常
if (isClosed()) {
throw new SQLException("HikariDataSource " + this + " has been closed.");
}
if (fastPathPool != null) {
return fastPathPool.getConnection();
}
// See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
HikariPool result = pool;
// 延迟初始化连接池
// 典型的双重检验锁代码块
if (result == null) {
synchronized (this) {
result = pool;
if (result == null) {
validate();
LOGGER.info("{} - Starting...", getPoolName());
try {
pool = result = new HikariPool(this);
this.seal();
}
catch (PoolInitializationException pie) {
if (pie.getCause() instanceof SQLException) {
throw (SQLException) pie.getCause();
}
else {
throw pie;
}
}
LOGGER.info("{} - Start completed.", getPoolName());
}
}
}
return result.getConnection();
}
此段代码是获取数据库连接的入口,如果 HikariDataSource 没有采用 no-args 的构造方式,那么 HikariCP 将会延迟初始化连接池,即在第一次获取数据库连接时初始化连接池。关于连接池的初始化问题,我们将在其他博文中另作讨论。
在此我们应该注意到:
- 获取数据库连接时,如果连接池没有初始化(fastPathPool 为 null)则要要进行连接池的初始化(初始化 pool);
- fastPathPool 与 pool 同为 HikariDataSource 的成员变量,分别对应于数据源的两种初始化方式;
private final HikariPool fastPathPool;
// See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
private volatile HikariPool pool;
- HikariCP 没有让初始化数据源没有使用同一个连接池,fastPathPool 相比于 pool ,没有 volatile 关键字修饰,可以看出在性能优化方面所做的细节。虽然 volatile 变量的读取性能已经和非 volatile 变量的读取非常接近。
可以看到,获取连接的主要过程就是通过 ConcurrentBag 的 borrow 方法获取 PoolEntry,然后通过获取到的 PoolEntry 创建物理连接,并返回代理连接 ProxyConnection。
为什么是 ProxyConnection
可以看出获取到的数据库连接最终是通过 ProxyFactory#getProxyConnection 方法创建了一个代理连接 ProxyConnection . 代码内容如下:
Connection createProxyConnection(final ProxyLeakTask leakTask, final long now)
{
return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
}
其中 connection 为 PoolEntry 的成员变量。也就是说获取的数据库连接是基于 PoolEntry.connection 由 ProxyFactory#getProxyConnection 方法创建的代理连接。
我们看下 ProxyFactory#getProxyConnection 方法的内容,其源码如下:
/**
* Create a proxy for the specified {@link Connection} instance.
* @param poolEntry the PoolEntry holding pool state
* @param connection the raw database Connection
* @param openStatements a reusable list to track open Statement instances
* @param leakTask the ProxyLeakTask for this connection
* @param now the current timestamp
* @param isReadOnly the default readOnly state of the connection
* @param isAutoCommit the default autoCommit state of the connection
* @return a proxy that wraps the specified {@link Connection}
*/
static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit)
{
// Body is replaced (injected) by JavassistProxyFactory
throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
}
可以看到方法中并没有具体的创建数据库的代理连接的代码内容,但从抛出异常之前的注释可以看出在会通过 JavassistProxyFactory 生成具体的代码。编译生成的代码会替换掉 getProxyConnection 的方法体。
getProxyConnection 的方法体是 JavassistProxyFactory#generateProxyClass 生成的。其字节码技术为 javassist,之所以采用 javassist,是因为其生成的字节码更加精简。这一点在官方 wiki 有提到,也是 HikariCP 表现得性能很快的原因之一。
我们可以注意到实际的内容源码中的内容有明显的差异:
实际的 ProxyFactory#getProxyConnection :
static ProxyConnection getProxyConnection(PoolEntry var0, Connection var1, FastList<Statement> var2, ProxyLeakTask var3, long var4, boolean var6, boolean var7) {
return new HikariProxyConnection(var0, var1, var2, var3, var4, var6, var7);
}
com.zaxxer.hikari.pool.HikariProxyConnection#HikariProxyConnection:
protected HikariProxyConnection(PoolEntry var1, Connection var2, FastList var3, ProxyLeakTask var4, long var5, boolean var7, boolean var8) {
super(var1, var2, var3, var4, var5, var7, var8);
}
com.zaxxer.hikari.pool.ProxyConnection#ProxyConnection
protected ProxyConnection(PoolEntry poolEntry, Connection connection, FastList<Statement> openStatements, ProxyLeakTask leakTask, long now, boolean isReadOnly, boolean isAutoCommit) {
this.poolEntry = poolEntry;
this.delegate = connection;
this.openStatements = openStatements;
this.leakTask = leakTask;
this.lastAccess = now;
this.isReadOnly = isReadOnly;
this.isAutoCommit = isAutoCommit;
}
可以看出:
- ProxyFactory 中创建代理连接的内容与实际编译的结果完全不一样:源码中创建代理连接只是抛了个 IllegalStateException 异常,而编译后却是创建了 HikariProxyConnection 并返回。这是由于 HikariCP 通过 javassist 生成了具体的代理类字节码。生成的代理字节码的主要工具是 JavassistProxyFactory。
- 代理连接包含了 poolEntry、connection、leakTask 等引用。因此代理连接相较于 poolEntry 主要是提供了泄露检测的功能。
在 com.zaxxer.hikari.pool.HikariPool#getConnection(long) 中创建代理连接的过程如下:
return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
其中,leakTaskFactory.schedule(poolEntry)
即是创建连接泄露检测任务。详情如下:
ProxyLeakTask schedule(final PoolEntry poolEntry)
{
return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
}
即:
- leakDetectionThreshold 为 0 时,不进行连接泄露检测;
- leakDetectionThreshold 不为 0 时,进行连接泄露检测;
关于连接泄露检测的详细内容,可以移步本人的关于 HikariCP leakDetectionThreshold 的博客。
总结
获取连接的过程大致为:
- 连接从连接池 fastPathPool 或者 pool(两者对应的初始化方式不同);
- 获取连接时,HikariPool 从其资源集合 ConcurrentBag 借用(borrow)PoolEntry,PoolEntry 是数据库物理连接的一对一封装;
- 根据借用到的 PoolEntry,创建代理连接并返回;