hikaril连接sql2000_hikari连接池解析(版本:HikariCP-2.5.1.jar)
maxLifetime参数
maxLifetime参数必须小于数据库的time_wait,默认是1800000,即30分钟。如果设置为0,表示存活时间无限大。如果不等于0且小于30秒则会被重置回30分钟。HikariConfig类中有该参数的校验规则。
HikariPool类中,当我们初始化连接池的时候,它的构造方法中,实例化了this.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();该类实现了Callable接口,用来初始化连接。
public Boolean call() throws Exception {
for(long sleepBackoff = 250L; HikariPool.this.poolState == 0 && HikariPool.this.totalConnections.get() < HikariPool.this.config.getMaximumPoolSize(); sleepBackoff = Math.min(TimeUnit.SECONDS.toMillis(10L), Math.min(HikariPool.this.connectionTimeout, (long)((double)sleepBackoff * 1.5D)))) {
PoolEntry poolEntry = HikariPool.this.createPoolEntry();
if (poolEntry != null) {
HikariPool.this.totalConnections.incrementAndGet();
HikariPool.this.connectionBag.add(poolEntry);
return Boolean.TRUE;
}
UtilityElf.quietlySleep(sleepBackoff);
}
return Boolean.FALSE;
}
复制代码
在其中调用createPoolEntry()生成一个连接。
private PoolEntry createPoolEntry() {
try {
final PoolEntry poolEntry = this.newPoolEntry();
long maxLifetime = this.config.getMaxLifetime();
if (maxLifetime > 0L) {
long variance = maxLifetime > 10000L ? ThreadLocalRandom.current().nextLong(maxLifetime / 40L) : 0L;
long lifetime = maxLifetime - variance;
poolEntry.setFutureEol(this.houseKeepingExecutorService.schedule(new Runnable() {
public void run() {
HikariPool.this.softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false);
}
}, lifetime, TimeUnit.MILLISECONDS));
}
this.LOGGER.debug("{} - Added connection {}", this.poolName, poolEntry.connection);
return poolEntry;
} catch (Exception var8) {
if (this.poolState == 0) {
this.LOGGER.debug("{} - Cannot acquire connection from data source", this.poolName, var8);
}
return null;
}
}
复制代码
在该方法中,设置了一个延时任务,具体的延时执行时间是根据maxLifetime来计算,触发时间距离maxlifetime的差值是根据 maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
来计算(up to 2.5% of the maxlifetime),在连接存活将要到达maxLifetime之前触发evit,用来防止出现大面积的connection因maxLifetime同一时刻失效。
当被触发时,会标记evict为true,标记为evict只是表示连接池中的该连接不可用,但还在连接池当中,还会被borrow出来,只是getConnection的时候判断了,如果是isMarkedEvicted,则会从连接池中移除该连接,然后close掉。
HikariCP中通过独立的线程池closeConnectionExecutor进行物理连接的关闭。出现以下三种情况时会触发连接的自动关闭:
连接断开;
连接存活时间超出最大生存时间(maxLifeTime)
连接空闲时间超出最大空闲时间(idleTimeout)
closeConnectionExecutor关闭连接后,会调用fillPool()方法对连接池进行连接填充
validationTimeout
validationTimeout用来指定验证连接有效性的超时时间(默认是5秒,最小不能小于250毫秒),在HikariPool.getConnection方法中会调用isConnectionAlive(Connection connection)对连接进行验证。
如果是jdbc4的话,可以使用isUseJdbc4Validation,是直接利用connection.isValid(validationSeconds)来验证连接的有效性;否则的话则用connectionTestQuery查询语句来查询验证。
leakDetectionThreshold`
该参数主要用来开启连接泄漏检测,在通过getConnection()获取连接的时候,会继续调用另外一个createProxyConnection()方法获取连接,这里我们关注入参this.leakTask.schedule(poolEntry)。
public final Connection getConnection(long hardTimeout) throws SQLException {
this.suspendResumeLock.acquire();
long startTime = clockSource.currentTime();
try {
long timeout = hardTimeout;
do {
PoolEntry poolEntry = (PoolEntry)this.connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);
if (poolEntry == null) {
break;
}
long now = clockSource.currentTime();
if (!poolEntry.isMarkedEvicted() && (clockSource.elapsedMillis(poolEntry.lastAccessed, now) <= this.ALIVE_BYPASS_WINDOW_MS || this.isConnectionAlive(poolEntry.connection))) {
this.metricsTracker.recordBorrowStats(poolEntry, startTime);
//获取连接
Connection var10 = poolEntry.createProxyConnection(this.leakTask.schedule(poolEntry), now);
return var10;
}
复制代码
该schedule方法返回一个ProxyLeakTask对象
//返回 ProxyLeakTask
ProxyLeakTask schedule(PoolEntry bagEntry) {
return this.leakDetectionThreshold == 0L ? NO_LEAK : new ProxyLeakTask(this, bagEntry);
}
复制代码
这里判断leakDetectionThreshold参数是否为0,默认是0,不开启检测。否则,就会开启一个延时执行任务,时间正好为设置的leakDetectionThreshold值,该任务的作用就是用来抛出Apparent connection leak detected异常。
private ProxyLeakTask(ProxyLeakTask parent, PoolEntry poolEntry) {
this.exception = new Exception("Apparent connection leak detected");
this.connectionName = poolEntry.connection.toString();
//延时执行
this.scheduledFuture = parent.executorService.schedule(this, parent.leakDetectionThreshold, TimeUnit.MILLISECONDS);
}
复制代码
截取一部分异常,如下
22:14:49.096 volte-cmd-service-test [HikariPool-1 housekeeper] WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for com.mysql.jdbc.JDBC4Connection@429fe922, stack trace follows
java.lang.Exception: Apparent connection leak detected
at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)
at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.obtainConnection(AbstractSessionImpl.java:386)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:87)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:112)
at org.hibernate.internal.SessionImpl.connection(SessionImpl.java:489)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:215)
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:200)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle.doGetConnection(HibernateJpaDialect.java:414)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:177)
复制代码
也就是从我们获取这个连接开始,到归还连接之前的这一段时间,如果超过了leakDetectionThreshold,则会抛出上面的异常。
HouseKeeper
它是HikariPool中的一个内部类,实现了Runnable接口,主要就是对连接进行管理。在初始化HikariPool的时候,会创建一个scheduleWithFixedDelay任务(已固定延迟时间执行,就是说两个任务之间的时间间隔是固定的,但每个任务的执行时长可能是不定的,与scheduleFixedRate的区别就是,不管任务是否执行完,到点就执行下一次任务),默认30s执行一次,刷新配置,进行判断。
public HikariPool(HikariConfig config) {
super(config);
this.ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", TimeUnit.MILLISECONDS.toMillis(500L));
this.HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30L));
this.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();
this.connectionBag = new ConcurrentBag(this);
this.totalConnections = new AtomicInteger();
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
this.checkFailFast();
if (config.getMetricsTrackerFactory() != null) {
this.setMetricsTrackerFactory(config.getMetricsTrackerFactory());
} else {
this.setMetricRegistry(config.getMetricRegistry());
}
this.setHealthCheckRegistry(config.getHealthCheckRegistry());
this.registerMBeans(this);
ThreadFactory threadFactory = config.getThreadFactory();
this.addConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection adder", threadFactory, new DiscardPolicy());
this.closeConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection closer", threadFactory, new CallerRunsPolicy());
//创建定时任务类
if (config.getScheduledExecutorService() == null) {
ThreadFactory threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(this.poolName + " housekeeper", true);
this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, (ThreadFactory)threadFactory, new DiscardPolicy());
//传递false参数给这个方法,执行shutdown()方法之后,待处理的任务将不会被执行。
this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
//取消任务后,判断是否需要从阻塞队列中移除任务
this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true);
} else {
this.houseKeepingExecutorService = config.getScheduledExecutorService();
}
this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), this.houseKeepingExecutorService);
//初始化HouseKeeper
this.houseKeepingExecutorService.scheduleWithFixedDelay(new HikariPool.HouseKeeper(), 100L, this.HOUSEKEEPING_PERIOD_MS, TimeUnit.MILLISECONDS);
}
复制代码
时间回拨处理
在HouseKeeper的run方法中,会先对时间进行判断。
这里主要就是通过一个时间差来判断这个时间差返回内是否有时间回拨,在初始化的时候会通过下面构造方法生成一个时间戳
// previous=当前时间-30s(默认的定时任务间隔时间)
private HouseKeeper() {
this.previous = HikariPool.clockSource.plusMillis(HikariPool.clockSource.currentTime(), -HikariPool.this.HOUSEKEEPING_PERIOD_MS);
}
复制代码
当初始化完成的第一次30s后或者上次任务执行完的30s后,再执行该任务,如果当前的时间戳+128ms还要小于previous(上次执行后减去30s的时间戳)+30s,则表示有时间回拨
//检测逆行时间,根据NTP规范允许+128ms
if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {
HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));
this.previous = now;
HikariPool.this.softEvictConnections();
HikariPool.this.fillPool();
return;
}
复制代码
此时,会打印日志,并重置previous为当前时间,设置连接为不可用,再重新生成连接。
保持最小连接
如果时间没有错误,则会判断idleTimeout,如果大于0,取出当前空闲连接
判断是否大于最小连接数minimumIdle,如果大于,则继续对当前的空闲连接基于lastAccessed(最后一次访问时间)进行排序,再遍历
如果取出的每个连接的空闲时间已经超过了idleTimeout,并且成功将连接从NOT_IN_USE(闲置中)更改为RESERVED(标记为保留中)
则关闭该连接
最后再新创建连接
public void run() {
try {
//刷新connectionTimeout、validationTimeout
HikariPool.this.connectionTimeout = HikariPool.this.config.getConnectionTimeout();
HikariPool.this.validationTimeout = HikariPool.this.config.getValidationTimeout();
HikariPool.this.leakTask.updateLeakDetectionThreshold(HikariPool.this.config.getLeakDetectionThreshold());
long idleTimeout = HikariPool.this.config.getIdleTimeout();
long now = HikariPool.clockSource.currentTime();
//时钟回拨判断
if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {
HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));
this.previous = now;
HikariPool.this.softEvictConnections();
HikariPool.this.fillPool();
return;
}
if (now > HikariPool.clockSource.plusMillis(this.previous, 3L * HikariPool.this.HOUSEKEEPING_PERIOD_MS / 2L)) {
HikariPool.this.LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));
}
this.previous = now;
String afterPrefix = "Pool ";
if (idleTimeout > 0L) {
//取出空闲连接 连接状态,IN_USE(1:使用中)、NOT_IN_USE(0:闲置中)、REMOVED(-1:已移除)、RESERVED(-1:标记为保留中)
List idleList = HikariPool.this.connectionBag.values(0);
int removable = idleList.size() - HikariPool.this.config.getMinimumIdle();
if (removable > 0) {
HikariPool.this.logPoolState("Before cleanup ");
afterPrefix = "After cleanup ";
//排序
idleList.sort(PoolEntry.LASTACCESS_COMPARABLE);
Iterator var8 = idleList.iterator();
while(var8.hasNext()) {
PoolEntry poolEntry = (PoolEntry)var8.next();
//idleTimeout判断,连接状态修改
if (HikariPool.clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && HikariPool.this.connectionBag.reserve(poolEntry)) {
HikariPool.this.closeConnection(poolEntry, "(connection has passed idleTimeout)");
--removable;
if (removable == 0) {
break;//keep min idle cons
}
}
}
}
}
HikariPool.this.logPoolState(afterPrefix);
HikariPool.this.fillPool();
} catch (Exception var10) {
HikariPool.this.LOGGER.error("Unexpected exception in housekeeping task", var10);
}
}
}
复制代码
问题
minimumIdle不一致问题
当前版本在应用初始化的时候,连接池也会进行初始化,但是当我们配置的数据源属性minimumIdle
//HikariCP-3.4.5.jar
private synchronized void fillPool()
{
final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
- addConnectionQueueReadOnlyView.size();
if (connectionsToAdd <= 0) logger.debug("{} - Fill pool skipped, pool is at sufficient level.", poolName);
//生成的个数减去了1
for (int i = 0; i < connectionsToAdd; i++) {
addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
}
}
//HikariCP-2.5.1.jar 的该方法
for (int i = 0; i < connectionsToAdd; i++) {
addBagItem();
}
复制代码
超时问题
错误日志如下:
java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
复制代码首先检查配置是否有问题,maxLifetime不能大于数据库的time_wait,查询mysql配置show variables like ‘%timeout%’,默认为8小时。
配置没有问题,则有可能与HikariCP无关。这个错误的产生原因就是请求向池中borrow时,没有可用连接超时导致。第一点,此时我们要思考我们的连接池数量设置是否合理,与业务量相关;第二点,看我们代码是否存在慢sql;第三点,与使用的持久层框架有关,分析我们的连接到底是被谁所持有,它的连接管理方法是怎么样的,什么情况下才会归还连接。
这篇文章就是因为我遇到这个错而无法定位才决定好好研究下的,我的这个错误产生的原因就是上面的第三点,我的项目采用的是jpa做数据库交互,且是一个非常简单的单表查询,连接应该很快归还池中才对,但是经过我的测试,当经过数据库查询以后,连接并没有被释放,反而是在我的整个会话结束后,才会归还连接。
jpa的核心是hibernate-core,在网上查询了hibernate的连接释放策略,知道了原因。hibernate 中连接释放的策略hibernate. connection. release_ mode有以下四种属性:
on_close,当Session被显式关闭或被断开连接时,才会释放JDBC连接
after_transaction,每次事务结束都会释放链接
after_statement,在每次JDBC调用后,都会主动的释放连接
auto,为JTA和CMT事务策略选择after_statement, 为JDBC事务策略选择after_transaction
我的springboot项目版本为1.x,即便将hibernate-core升级到较高版本,并开启事物,也还是基于on_close方式去释放连接;我测试了2.x版本的,不开启事物时也是on_close,开启事物后,就成了after_transaction,具体1.x版本为何开启事物也不生效还不清楚。
参考资料
流程图的方式讲解,很清晰易懂,版本也比较高
HikariCP是如何管理数据库连接的?
关于找一找教程网
本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。
本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。
[hikari连接池解析(版本:HikariCP-2.5.1.jar)]http://www.zyiz.net/tech/detail-143838.html
hikaril连接sql2000_hikari连接池解析(版本:HikariCP-2.5.1.jar)相关推荐
- mysql api 连接池_SpringBoot-整合HikariCP连接池
HikariCP连接池概述池化思想 池化思想是我们项目开发过程中的一种非常重要的思想,如整数池,字符串池,对象池.连接池.线程池等都是池化思想的一种应用,都是 通过复用对象,以减少因创建和释放对象所带 ...
- mybatis连接mysql数据库连接池_对于数据库连接池的一些思考和MyBatis的集成与使用...
Java应用要连接数据库需要先通过jdbc与数据库之间产生connection,然后通过sql语句产生statment,再执行这个statment查询的到ResultSet返回给应用,应用解析Resu ...
- golang 数据库 连接与连接池
database/sql database/sql是golang的标准库之一,它提供了一系列接口方法,用于访问关系数据库.它并不会提供数据库特有的方法,那些特有的方法交给数据库驱动去实现. datab ...
- 性能测试能力提升-长连接、短连接、连接池
目录 一.背景 二.长连接.短连接 三.连接池的作用 四.连接池配置定义 五.连接池设置考虑的因素 六.Redis连接池补充知识 一.背景 接着上一篇的知识:性能测试能力提升-基准.负载.压力.容量测 ...
- redis:redis介绍和安装、普通连接和连接池、redis 5大数据类型之字符串、Hash、列表、其他操作(通用)、管道、django使用redis、接口缓存
目录 一. redis介绍和安装 二. 普通连接和连接池 三. redis 5大数据类型之字符串 四. redis 5大数据类型之Hash 五. redis 5大数据类型之列表 六. 其他操作(通用) ...
- MySQL之长连接、短连接、连接池(转载:http://www.ywnds.com/?p=9801)
当数据库服务器和客户端位于不同的主机时,就需要建立网络连接来进行通信.客户端必须使用数据库连接来发送命令和接收应答.数据.通过提供给客户端数据库的驱动指定连接字符串后,客户端就可以和数据库建立连接了. ...
- MySQL性能优化知识:长连接、短连接、连接池
当数据库服务器和客户端位于不同的主机时,就需要建立网络连接来进行通信.客户端必须使用数据库连接来发送命令和接收应答.数据.通过提供给客户端数据库的驱动指定连接字符串后,客户端就可以和数据库建立连接了. ...
- 主机ssh升级到6.7以上版本后,使用jsch jar包ssh连接不上报Algorithm negotiation fail问题的解决办法
文章目录 一. 解决方案 1. 添加加密算法 2. 升级版本 3. 回退版本 4. j2ssh.jar方式替代jsch 5. jsch版本下载列表 一. 解决方案 1. 添加加密算法 ssh连接问题是 ...
- php mysql长连接聊天室_PHP之探索MySQL 长连接、连接池
PHP连接MysqL的方式,用的多的是MysqL扩展.MysqLi扩展.pdo_MysqL扩展,是官方提供的.PHP的运行机制是页面执行完会释放所有该PHP进程中的所有资源的,如果有多个并发访问本地的 ...
最新文章
- 插入始终是1_40分!1分钟4次!大JB太硬了!
- 用逻辑回归模型解决互联网金融信用风险问题
- Objective-C 内存管理retain和release
- 计算器软件----表达式求值
- 完整iOS APP发布App Store上架流程
- 利用C++语言设计可扩展线程池
- Linux 10分钟掌握Linux常用开发工具及编译的四个过程
- 简便无刷新文件上传系统
- 小程序 getphonenumber_小程序入门,看这一篇就够了!
- python 在线培训费用-在线Python编程培训哪家机构比较好?
- 无线视频服务器家里好用吗,短视频APP为什么选择国内大带宽服务器?国内服务器有什么优势...
- 电商数据分析Excel案例
- GLASS 产品使用(一)
- php中empty检测非空,php empty() 检查一个变量是否为空
- 分布式 | dble 读写分离场景下为什么普通的读 sql 发送到了 master 实例上
- Hark语音识别学习(二)--HARK数据类型
- 编程新技术实务实验二HTML以及J2EE简单编程
- AMR中的RO,RW和ZI
- 华为服务器批量修改bmc地址,华为服务器批量修改bmc地址
- TCGA 亚型突变负荷代码