最近遇到了数据库链接池的链接被负载均衡器关闭之后,应用端没有准确捕获到无效链接,导致最终报错。所有很想知道HikariCP探活机制如何保证链接有效的。关键的参数如下:
connection-timeout: 30000 #可接受的最低连接超时为250 ms。 默认值:30000(30秒)
idle-timeout: 60000
max-lifetime: 600000
本来以为idle-timeout空闲失效时间,如果链接超过了这个数值,连接池会主动去续链接时间或者给清理掉。续链接时间是因为这个链接还需要使用,不能被关闭,所以需要续。清理掉是因为不需要了,可以清理掉了。这里和数据库连接池最小数有关系了。常规的理解,数据库连接池维护最小链接,那肯定得保证这些最小链接是有效的。
按照一些连接池的建议规范,将最小和最大设置成一样大,做成一个固定大小的连接池。然后发现,idle-timeout在固定大小连接池情况下,直接失效了,失效了就是没有用处。
一下就懵了,这怎么搞,链接无效了怎么弄?
还有就是,就算是个可变的链接池大小,如果链接数达到了最小链接,idle-timeout也会失效,不会去做空闲超时处理。
然后我发现还有一个参数max-lifetime,直接字面意思理解是链接的最大生命周期。有了这个参数,那是不是idle-time就只是用来做连接池中大于核心连接池数的链接剔除操作?
这都是官网文档和字面意思的理解。其实从这些参数以及对一个数据库连接池的假想认知(从功能使用角度),这么流行的一个数据库连接池,肯定要做空闲剔除、链接续时间或者重新获取链接的操作的。
最后走上了源码路。
怎么撸源码呢,有maven能链接仓库就行。直接拉取源码文件,sts工具点击就能看源码,带注释的。
如何进去代码呢,直接从配置文件进去。yml文件里配置了hikari的配置,直接点解就能链接到配置,然后看看配置具体是什么个意思,可能发现宝藏。
看不懂单词的我,查了下Evict,代表剔除。是不是说剔除无效链接的??然后看代码注释吧。
/**
* Evict a connection from the pool. If the connection has already been closed (returned to the pool)
* this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
* currently in use. If the connection has not been closed, the eviction is immediate.
* 链接被归还到连接池可能做软剔除,然后未来一个时间真正剔除。
* 如果链接被关闭了,立刻剔除。
*
* @param connection the connection to evict from the pool
*/
public void evictConnection(Connection connection)
{
HikariPool p;
if (!isClosed() && (p = pool) != null && connection.getClass().getName().startsWith("com.zaxxer.hikari")) {
p.evictConnection(connection);
}
}
这个方法是在DataSource里,这是提供的对外口,让调用者去主动剔除?谁是调用者,DataSource一般是数据库链接配置。p这里是HikariPool,那就是链接池做剔除操作,那剔除的条件是什么?
/**
* "Soft" evict a Connection (/PoolEntry) from the pool. If this method is being called by the user directly
* through {@link com.zaxxer.hikari.HikariDataSource#evictConnection(Connection)} then {@code owner} is {@code true}.
* 如果链接是空闲的,直接关闭,否则只能做标记。这里说链接是空闲的,最小链接里的链接能被标记为空闲么?
* If the caller is the owner, or if the Connection is idle (i.e. can be "reserved" in the {@link ConcurrentBag}),
* then we can close the connection immediately. Otherwise, we leave it "marked" for eviction so that it is evicted
* the next time someone tries to acquire it from the pool.
*
* @param poolEntry the PoolEntry (/Connection) to "soft" evict from the pool
* @param reason the reason that the connection is being evicted
* @param owner true if the caller is the owner of the connection, false otherwise
* @return true if the connection was evicted (closed), false if it was merely marked for eviction
*/
private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
{
poolEntry.markEvicted();
if (owner || connectionBag.reserve(poolEntry)) {
closeConnection(poolEntry, reason);
return true;
}
return false;
}
如果链接是空闲的,直接关闭,否则只能做标记。这里说链接是空闲的,最小链接里的链接能被标记为空闲么?如果不能,那么最小链接里的链接永远不会被剔除清理,不被清理是合理的,如果都清理了,和保持最小链接就矛盾了。
反证法,最小链接不被剔除。
那接下来继续找还有没有剔除逻辑或者链接失效判断逻辑?
获取链接的几个方法
/** {@inheritDoc} */
@Override
public int getActiveConnections()
{
return connectionBag.getCount(STATE_IN_USE);
}
看一下都有哪些状态。
int STATE_NOT_IN_USE = 0; //字面意思理解
int STATE_IN_USE = 1;
int STATE_REMOVED = -1;
int STATE_RESERVED = -2;
连接池分了4中链接状态,既然区分了状态,那就是说有一个地方专门存链接,每个链接都能被清晰的标注。打标之后才能更好的工作。
/** {@inheritDoc} */
@Override
public int getIdleConnections()
{
return connectionBag.getCount(STATE_NOT_IN_USE);
}
空闲链接是没有使用状态的链接。什么是没有使用呢?多久不用代码没有使用?
/**
* The house keeping task to retire and maintain minimum idle connections.
* 管家任务保持最小链接数。管家任务是一个线程。
*/
private final class HouseKeeper implements Runnable
{
private volatile long previous = plusMillis(currentTime(), -housekeepingPeriodMs);
//housekeepingPeriodMs 固定值30秒钟
//当期服务器时间减去30秒
@Override
public void run()
{
try {
// refresh values in case they changed via MBean
connectionTimeout = config.getConnectionTimeout();
validationTimeout = config.getValidationTimeout();
//leak detection 泄露检查 leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
catalog = (config.getCatalog() != null && !config.getCatalog().equals(catalog)) ? config.getCatalog() : catalog;
//终于找到关键的ideltimeout了。。。。。。
//看下是怎么用的?
final long idleTimeout = config.getIdleTimeout();
final long now = currentTime();
//NTP 知道是什么吗,时钟同步用的。网络时间协议。
// Detect retrograde time, allowing +128ms as per NTP spec.
//如果最新的时间+128毫秒,比前面时间还要小,说明服务器时钟中途被修改了,系统会提示。
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)) {
//当期时间与前一个时间比较,超过45秒。也是时钟不对
// 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));
}
//以上对时钟有要求,【左偏慢128ms,右偏快45s】
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) {
//PoolEntry 有个属性lastAccessed,最后访问?什么东西,最后访问的一个时间戳。最后访问时间比当前时间要大于空闲时间
if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
closeConnection(entry, "(connection has passed idleTimeout)");
toRemove--;
}
}
}
//这里面有个思考?
//被标记了不再使用的才会被移除。
//如果正在使用,可是超时了,超过了最大的查询时间,也就是说遇到了一个慢查询,很慢很慢导致超过了5分钟,比如啊。这个5分钟服务器没有响应过来,会被haproxy识别为空闲无效访问直接断掉么?
// connectionBag.reserve(entry) 这段还没看明白,有时间再研究了。
logPoolState(afterPrefix);
fillPool(); // Try to maintain minimum connections
}
catch (Exception e) {
logger.error("Unexpected exception in housekeeping task", e);
}
}
}
原网址: 访问
创建于: 2023-07-31 10:29:19
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论