当前位置:网站首页>二、HikariCP获取连接流程源码分析二
二、HikariCP获取连接流程源码分析二
2022-06-25 19:09:00 【用户1422411】
欢迎访问我的博客,同步更新: 枫山别院
源代码版本2.4.5-SNAPSHOT
HikariPool的getConnection()方法
在上一篇《HikariCP获取连接流程源码分析一》中,我们分析了HikariDataSource的getConnection()
方法,而这个方法,其实详细的实现细节都是在HikariPool的getConnection()方法中,我们来分析下HikariPool的getConnection()方法。
代码如下:
public final Connection getConnection() throws SQLException {
return getConnection(connectionTimeout);
}
这里又调用了一个有参的getConnection()方法,但是我们并没有传参数connectionTimeout,这个是哪里来的呢?这个其实就是用户在初始化连接池的时候设置的参数connectionTimeout
,它表示获取连接的超时时间,不配置的话默认值 30秒。我们继续看下getConnection(connectionTimeout);
的实现:
public final Connection getConnection(final long hardTimeout) throws SQLException {
//①
//获取连接的时候申请令牌, 主要是为了连接池挂起的时候, 控制用户不能获取连接
//当连接池挂起的时候, Semaphore的 10000 个令牌都会被占用, 此处就会一直阻塞线程等待令牌
suspendResumeLock.acquire();
//记录获取连接的开始时间, 用于超时判断
final long startTime = clockSource.currentTime();
try {
long timeout = hardTimeout;
do {
//②
//从连接池获取连接, 超时时间timeout
final PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
//borrow方法在超时的时候才会返回 null
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
final long now = clockSource.currentTime();
//③
//获取连接的时候, 判断连接是否已经被标记移除
if (poolEntry.isMarkedEvicted() || (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
//如果连接超出maxLifetime, 或者连接测试不通过, 就关闭连接
closeConnection(poolEntry, "(connection is evicted or dead)"); // Throw away the dead connection (passed max age or failed alive test)
//剩余超时时间
timeout = hardTimeout - clockSource.elapsedMillis(startTime);
} else {
//④
//记录连接借用
metricsTracker.recordBorrowStats(poolEntry, startTime);
//创建ProxyConnection, ProxyConnection是Connection的包装, 同时也创建一个泄露检测的定时任务
return poolEntry.createProxyConnection(leakTask.schedule(poolEntry), now);
}
} while (timeout > 0L);
} catch (InterruptedException e) {
throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
} finally {
//释放锁
suspendResumeLock.release();
}
//⑤
//获取连接超时才会执行下面的代码
logPoolState("Timeout failure ");
metricsTracker.recordConnectionTimeout();
String sqlState = null;
final Throwable originalException = getLastConnectionFailure();
if (originalException instanceof SQLException) {
sqlState = ((SQLException) originalException).getSQLState();
}
final SQLException connectionException = new SQLTransientConnectionException(poolName + " - Connection is not available, request timed out after " + clockSource.elapsedMillis(startTime) + "ms.", sqlState, originalException);
if (originalException instanceof SQLException) {
connectionException.setNextException((SQLException) originalException);
}
throw connectionException;
}
①Semaphore
suspendResumeLock.acquire();
//记录获取连接的开始时间, 用于超时判断
final long startTime = clockSource.currentTime();
getConnection的第一步,首先是获取令牌。我们从变量的名字suspendResumeLock
来看,可能跟挂起(suspend)有关,那么挂起什么东西?如果之前大家有读过 HikariCP 的文档,或者使用过HikariCP的挂起功能,那么你肯定已经猜到了,这个是跟挂起整个连接池有关。
- 挂起HikariCP
HikariCP的挂起功能,其实就是暂停用户获取连接,也就是说,挂起整个连接池之后,如果有线程要从连接池获取连接,那么会一直阻塞,直到连接池被恢复。
挂起有什么用?
作者 brett 提到挂起的使用方法:
- 挂起连接池
- 更改数据库连接池配置,或者更改 DNS 配置(指向新的主服务器)
- 软驱逐连接池中现有的连接
- 恢复连接池
HikariCP可以在运行期通过 JMX修改一些配置的(并不是所有的配置), 有:connectionTimeout(获取连接的超时时间),leakDetectionThreshold(连接泄露检测时间),maxPoolSize(连接池最大连接数),minIdle(最小空闲连接数),maxLifetime(连接最大存活时间),idleTimeout(连接最大空闲时间),共 7 项。
比如我挂起了连接池,然后修改了maxLifetime,那么连接池中现有的连接还是之前的配置,我就要将所有的连接都从连接池中驱逐出去,然后恢复连接池,这时候连接池就会使用新的配置创建新的连接。
除此之外,还可以使用连接池挂起时,线程一直阻塞无法获取到连接这个特性,来模拟数据库连接故障,来测试应用。
- 怎么实现的
OK,我们知道了这一句代码的目的主要是挂起连接池时,阻止用户获取连接的。那么是怎么实现的呢?
其实,suspendResumeLock
的类是com.zaxxer.hikari.util.SuspendResumeLock
,它的内部是用Semaphore
实现的。Semaphore
是 java 的concurrent包下的 并发工具类,它用给线程发放令牌的方式,控制线程的并发数量。
举个场景例子,假如是秒杀:我们知道服务器的最大并发处理能力是同时处理 1000 个请求,超过 1000 个请求服务器可能会宕机,在不扩容的情况下,尽量保证服务可用。这个时候,我们就要控制用户的请求数量不能超过 1000对吧。
这时候我们可以用Semaphore
实现,Semaphore
类似一个令牌桶,桶里可以放指定数量的令牌,在并发的时候,每个线程从桶里拿一个(也可以是多个)令牌,拿到令牌的才可以继续执行,拿不到令牌的就等着(根据策略不同,也可能是抛异常等),直到有其他线程释放令牌之后,得到令牌继续执行。
上面的场景,我们可以使用Semaphore
初始化 1000 个令牌,每个线程拿一个令牌,这样我们就可以控制同时处理的请求数量不超过 1000了吧。
同样的道理,我们看下挂起连接池的方法:
public void suspend() {
//MAX_PERMITS = 10000
acquisitionSemaphore.acquireUninterruptibly(MAX_PERMITS);
}
HikariCP 在这里初始化了 1万个令牌,如果用户调用了suspend()
挂起连接池,其实就是调用了Semaphore
一次获取 1 万个令牌,这样其他线程就没有令牌可以拿了,只能一直等,直到用户恢复线程池,释放这 1 万个令牌到桶里。
需要注意的是,要使用挂起连接池的功能,必须配置isAllowPoolSuspension=true
,否则使用挂起功能会报错。
我们提到isAllowPoolSuspension
其实是还要说一下suspendResumeLock的一个优化点。
在初始化连接池的时候,这个suspendResumeLock
根据你是否开启了挂起功能,会有不同的实现,this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
。
假如没有启用挂起功能,那么suspendResumeLock
是一个FAUX_LOCK
。
FAUX_LOCK
是什么呢?看代码:
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() {}
};
你没有看错,就是一个空实现,方法里什么都没有。
这么做有什么好处?其实是,当挂起功能没有开启的时候, 它会提供一个空实现, 希望 JIT 能将之优化掉。也就是说,每次申请令牌其实是调用空方法,什么都不干,代码在运行多次之后,JIT 有可能会把它优化掉,根本就不调用了。这样,我们每次获取连接的时候,会节省申请令牌的额外开销,提高性能。
最快的一般不是浪得虚名,肯定都有值得我们学习的地方......你学会了没有?
我们继续分析。
clockSource
是一个时间的工具类,用于获取当前时间,计算时间差等等。此处记录了当前时间,用于后面时间差计算,判断获取连接是否超时用的。
②获取连接
//②
//从连接池获取连接, 超时时间timeout
final PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
//borrow方法在超时的时候才会返回 null
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
此处的代码,我们可以看到,从connectionBag
中获取了一个poolEntry
对象。poolEntry
其实是对数据库连接的一个包装类,connectionBag
才是 HikariCP中实际保存数据库连接的容器,里面是一个CopyOnWriteArrayList
。由于connectionBag
非常重要,我们要在后面单独分析,此处不深入进去了。
但是从connectionBag
获取连接的时候,我可以看到传了一个参数timeout
,这个timeout
就是我们配置的connectionTimeout
,获取连接的超时时间,如果在指定的timeout
时间内,没有返回一个连接,那么就返回一个 null。
此时已经超时了,所以下面的判断就是跳出循环,不在尝试获取连接了。
由于放在一篇文章里太长,未尽事宜,下篇继续!
边栏推荐
- Analysis on development scale and development trend of China's night economy industry in 2021 [figure]
- Tcp/ip test questions (II)
- Apifox简单了解——WEB端测试的集大成者
- PHP database connection version1.1
- QQ robot: self forbidden words management of group members [latest beta2 version]
- 最新數據挖掘賽事方案梳理!
- Connecting PHP to MySQL instances in the lamp environment of alicloud's liunx system
- 一晚上做了一个xpath终结者:xpath-helper-plus
- Solidity date tool
- Do you want to know how new investors open accounts? Is online account opening safe?
猜你喜欢
Redis configuration (Internet access, password)
两轮市场红海,利尔达芯智行如何乘风破浪?
In 2021, China's private equity market is growing, and the scale of private equity fund management reaches 19.78 trillion yuan [figure]
One night I worked as an XPath Terminator: XPath Helper Plus
华为发布两大昇腾计划 推动AI人才发展和科研创新
What is an operator?
158_ Model_ Power Bi uses DAX + SVG to open up almost all possibilities for making business charts
最新数据挖掘赛事方案梳理!
ECS 7-day practical training camp (Advanced route) -- day01 -- setting up FTP service based on ECS
QQ robot epidemic situation query / epidemic situation concern [latest beta2 version]
随机推荐
Is it safe for tongdaxin to open an account?
最新數據挖掘賽事方案梳理!
初探Oracle全栈虚拟机---GraalVM
What is an operator?
Tcp/ip test questions (V)
Analysis on planting area, output and import of sugarcane in Guangxi in 2021: the output of sugarcane in Guangxi accounts for 68.56% of the total output of sugarcane in China [figure]
SVN介绍及使用总结
LeetCode-101-对称二叉树
最新数据挖掘赛事方案梳理!
【C语言练习——打印上三角及其变形(带空格版)】
Tcp/ip test questions (I)
Analysis on market scale and supply of China's needle coke industry in 2020 [figure]
Analysis on the development trend of China's intense pulsed light equipment industry in 2021: the market scale is growing, and the proportion of imported brands is large [figure]
JVM | runtime data area (heap space)
R语言plotly可视化:plotly可视化二维直方图等高线图(Basic 2D Histogram Contour)
Tcp/ip test questions (II)
Kotlin compose terminate todo project Click to edit and modify todo
Oriental Wealth function (the most complete edition of Childe Yong)
Jump jump games auxiliary (manual version) py code implementation
solidity获取季度时间