如何设计一款高性能的数据库连接工具?过来看这里,这是一款号称拥有“光”一样链接速度的工具,我们看它是怎样实现的(第二节:Hikari是如何实现的底层逻辑)
续接上节如何设计一款高性能的数据库连接工具?过来看这里,这是一款号称拥有“光”一样链接速度的工具,我们看它是怎样实现的(第一节:数据库链接技术发展)-CSDN博客
接下来我们继续分析Hikari,它为什么怎么快呢?因为HikariCP没有spring.factories(在spring boot应用启动中会获取应用的ClassLoader进而获得所有jar包并且将读取每个jar包下的META-INF/spring.factories)接下来所以我们从代码结构进行分析
1.HikariDataSource类继承图
DataSource代表数据库连接,而HikariDataSource就是Hikari数据库连接,就是Hikari的核心,下面看一下它的类图
HikariDataSource 继承了 HikariConfig,实现了 DataSource 接口和Closeable接口
- HikariConfig,是Hikari的配置类,有着jdbcUrl、用户名密码、超时时间、数据库连接池连接数等成员变量属性
- DataSource,是一个接口(javax.sql.DataSource),就是数据库连接池在Java类中的体现,对JDBC框架来说不需要关注Connection怎么来的,只需要调这个接口的方法获取即可
DataSource里面定义了两个方法:一个是无参数的获取连接、一个是通过用户名和密码获取数据库连接的
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
HikariConfig里面定义了Hikari连接池的参数,有着jdbcUrl、用户名密码、超时时间、数据库连接池连接数等成员变量属性,其中构造函数中默认初始化了一些参数值
public HikariConfig()
{
dataSourceProperties = new Properties();
healthCheckProperties = new Properties();
minIdle = -1;
maxPoolSize = -1;
maxLifetime = MAX_LIFETIME;
connectionTimeout = CONNECTION_TIMEOUT;
validationTimeout = VALIDATION_TIMEOUT;
idleTimeout = IDLE_TIMEOUT;
initializationFailTimeout = 1;
isAutoCommit = true;
keepaliveTime = DEFAULT_KEEPALIVE_TIME;
String systemProp = System.getProperty("hikaricp.configurationFile");
if (systemProp != null) {
loadProperties(systemProp);
}
}
2.HikariDataSource的成员结构
- isShutdown,一个AtomicBoolean(cas乐观锁)用来标识该dataSource是否已经关闭
- fastPathPool 和 pool,都是指向HikariPool结构的引用(Hikari优化用来提升性能的手段)
- 若干继承自HikariConfig的数据库连接池配置属性
//实现DataSource接口里面的getConnection()方法
@Override
public Connection getConnection() throws SQLException
{
//判断数据库连接是否已经关闭
if (isClosed()) {
throw new SQLException("HikariDataSource " + this + " has been closed.");
}
//首先,从fastPathPool的连接池里面去获取连接
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();
}
3.HikariPool连接池管理
它继承了HikariPoolMXBean,实现了PoolBase类
连接池初始化流程
连接池源码
public HikariPool(final HikariConfig config)
{
//利用config初始化各种连接池属性,并且产生一个用于生产物理连接的数据源DriverDataSource
super(config);
//初始化存放连接对象的核心类ConcurrentBag
this.connectionBag = new ConcurrentBag<>(this);
//初始化型号量,用于控制获取连接的速度,isAllowPoolSuspension=true生效
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
//初始化一个延时任务线程池类型的对象houseKeepingExecutorService,用于后续执行一些延时/定时类任务,
// 比如连接泄漏检查延时任务,除此之外maxLifeTime后主动回收关闭连接也是交由该对象来执行的
this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
//预热连接池,HikariCP会在该流程的checkFailFast里初始化好一个连接对象放进池子内,
// 当然触发该流程得保证initializationTimeout > 0(默认值1),
// 这个配置属性表示留给预热操作的时间,默认值1在预热失败时不会发生重试
checkFailFast();
if (config.getMetricsTrackerFactory() != null) {
setMetricsTrackerFactory(config.getMetricsTrackerFactory());
}
else {
//注入MetricRegistry来实现对连接池指标的收集。这样我们可以较为方便的监控连接池的运行状态
setMetricRegistry(config.getMetricRegistry());
}
setHealthCheckRegistry(config.getHealthCheckRegistry());
handleMBeans(this, true);
ThreadFactory threadFactory = config.getThreadFactory();
final int maxPoolSize = config.getMaximumPoolSize();
LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(maxPoolSize);
this.addConnectionQueueReadOnlyView = unmodifiableCollection(addConnectionQueue);
//初始化一个线程池对象addConnectionExecutor,用于后续扩充连接对象
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());
//初始化一个线程池对象closeConnectionExecutor,用于关闭一些连接对象
this.closeConnectionExecutor = createThreadPoolExecutor(maxPoolSize, poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
//初始化连接泄露告警器
this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
//初始化扩/缩容定时器
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
if (Boolean.getBoolean("com.zaxxer.hikari.blockUntilFilled") && config.getInitializationFailTimeout() > 1) {
addConnectionExecutor.setMaximumPoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));
addConnectionExecutor.setCorePoolSize(Math.min(16, Runtime.getRuntime().availableProcessors()));
final long startTime = currentTime();
while (elapsedMillis(startTime) < config.getInitializationFailTimeout() && getTotalConnections() < config.getMinimumIdle()) {
quietlySleep(MILLISECONDS.toMillis(100));
}
addConnectionExecutor.setCorePoolSize(1);
addConnectionExecutor.setMaximumPoolSize(1);
}
}
- 利用config初始化各种连接池属性,并且产生一个用于生产物理连接的数据源DriverDataSource
- 初始化存放连接对象的核心类ConcurrentBag
- 初始化一个延时任务线程池类型的对象houseKeepingExecutorService,用于后续执行一些延时/定时类任务,比如连接泄漏检查延时任务,除此之外maxLifeTime后主动回收关闭连接也是交由该对象来执行的
- 预热连接池,HikariCP会在该流程的checkFailFast里初始化好一个连接对象放进池子内,当然触发该流程得保证initializationTimeout > 0(默认值1),这个配置属性表示留给预热操作的时间,默认值1在预热失败时不会发生重试
- 初始化一个线程池对象addConnectionExecutor,用于后续扩充连接对象
从上面的代码可以看到一个方法,checkFailFast(),快速失败。避免在初始化中因为多次尝试而造成性能损耗,而且HikariCP仅预热一个连接对象。
4.HouseKeeper管家
上面HikariPool的维护,主要是靠HouseKeeper来维护管理连接池中的链接,我们看错看代码发现HouseKeeper实现了Runnable接口,说明他是另起了一个分支线程来执行动作的
@Override
public void run()
{
try {
// refresh values in case they changed via MBean
connectionTimeout = config.getConnectionTimeout();
validationTimeout = config.getValidationTimeout();
leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
catalog = (config.getCatalog() != null && !config.getCatalog().equals(catalog)) ? config.getCatalog() : catalog;
final long idleTimeout = config.getIdleTimeout();
final long now = currentTime();
// Detect retrograde time, allowing +128ms as per NTP spec.
if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs)) {
logger.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
poolName, elapsedDisplayString(previous, now));
previous = now;
softEvictConnections();
return;
}
else if (now > plusMillis(previous, (3 * housekeepingPeriodMs) / 2)) {
// No point evicting for forward clock motion, this merely accelerates connection retirement anyway
logger.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", poolName, elapsedDisplayString(previous, now));
}
previous = now;
String afterPrefix = "Pool ";
if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) {
logPoolState("Before cleanup ");
afterPrefix = "After cleanup ";
final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);
int toRemove = notInUse.size() - config.getMinimumIdle();
for (PoolEntry entry : notInUse) {
if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
closeConnection(entry, "(connection has passed idleTimeout)");
toRemove--;
}
}
}
logPoolState(afterPrefix);
fillPool(); // Try to maintain minimum connections
}
catch (Exception e) {
logger.error("Unexpected exception in housekeeping task", e);
}
}
HouseKeeper主要工作如下:
- 检测系统时间的准确性,系统时间被用户篡改过,则处理清空连接池
- 对空闲的连接,根据idleTimeout属性清理过期连接
- 在初始化或清理过后,填充新的连接到连接池中
5.回顾一下
我们读代码,发现HikariCP实现原理,实现了Java定义的DataSource接口,可以实现DataSource链接的创建,所有的链接,通过HikariPool进行管理的,HikariPool提供了快速检测失败的方法,可以加快链接快速失败,最后就是提供了HouseKeeper来管理HikariPool连接池内容。
下一节我们将继续分享Hikari优化连接池存储容器ConcurrentBag
原文地址:https://blog.csdn.net/Scalzdp/article/details/142456424
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!