欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

DBCP连接池原理与配置分析【二】
(last modified Apr 20, 2024, 3:04 PM )
by
侧边栏壁纸
  • 累计撰写 176 篇文章
  • 累计创建 61 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

DBCP连接池原理与配置分析【二】

橙序员
2022-11-20 / 0 评论 / 0 点赞 / 590 阅读 / 2,173 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

对象连接池属性主要封装在BaseGenericObjectPool中,包括maxTotal、blockWhenExhausted、maxWaitDuration、lifo等默认配置属性。其中maxTotal表示最大连接数,blockWhenExhausted表示当连接用尽时是否阻塞等待,maxWaitDuration表示最大等待时间,lifo表示是否采用后进先出等策略。除此之外,还包括fairness、testOnCreate、testOnBorrow、testOnReturn、testWhileIdle、durationBetweenEvictionRuns、numTestsPerEvictionRun等属性。这些属性可根据具体需求进行配置,以提高连接池的性能和稳定性。

对象连接池属性主要封装在BaseGenericObjectPool中(GenericObjectPool的父类),对应的默认配置如下:

// Configuration attributes
private volatile int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
private volatile boolean blockWhenExhausted = BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED;
private volatile Duration maxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT;
private volatile boolean lifo = BaseObjectPoolConfig.DEFAULT_LIFO;
private final boolean fairness;
private volatile boolean testOnCreate = BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
private volatile boolean testOnBorrow = BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW;
private volatile boolean testOnReturn = BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN;
private volatile boolean testWhileIdle = BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE;
private volatile Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
private volatile int numTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;

private volatile Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
private volatile Duration softMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION;
private volatile EvictionPolicy<T> evictionPolicy;
private volatile Duration evictorShutdownTimeoutDuration = BaseObjectPoolConfig.DEFAULT_EVICTOR_SHUTDOWN_TIMEOUT;

对象连接池创建属性

maxTotal-连接池最大数量

maxTotal属性用于线程池初始化创建时使用。具体用法在连接池创建的方法中GenericObjectPool.create(),在连接池创建时已经讲过,不再赘述。

image-1713459894930

blockWhenExhausted-是否等待获取连接

blockWhenExhausted连接耗尽时是否等待获取连接,如果阻塞为true则在连接池耗尽时,根据我们设置的maxWaitDuration来尝试获取连接;如果为false则直接抛出异常,也是在连接池创建时使用。

image-1713459913188

maxWaitDuration-最大等待时间

blockWhenExhausted配合使用,当连接池连接耗尽时,是否等待maxWaitDuration再取获取连接。

需要注意的是,该属性值默认为-1,而代码里有这样的判断:

//borrowObject()
if (borrowMaxWaitDuration.isNegative()) {
    p = idleObjects.takeFirst();
} else {
    p = idleObjects.pollFirst(borrowMaxWaitDuration);
}

//borrowMaxWaitDuration.isNegative()
public boolean isNegative() {
      return seconds < 0;
}

也就是虽然我们设置了blockWhenExhausted为true,如果没设置等待时间maxWaitDuration,取连接时同样不会等待。

对象连接池使用属性

由于我们连接数据库时,mysql会给client端指定每个数据库连接的最大空闲等待时长,如果我们没有开启空闲线程管理的话(下面会讲),就需要在创建、使用时验证获取到的空闲连接的可用性。

testOnBorrow-使用前是否检测连接可用性

这个设置用来在我们执行真正的sql前,检测连接是否可用。默认不开启。

image-1713459943610

testOnCreate-创建连接后检测连接可用性

这个设置用在我们初始化连接池创建连接后,检测连接是否可用,默认不开启

空闲线程管理

讲解其他属性时,我们首先要了解不再使用的连接是如何回收的,线程池对连接的管理和我们其他业务一样,也是通过定时任务进行管理回收的,那么这个定时任务是如何开启的呢?

durationBetweenEvictionRuns-指定时间创建Evictor

durationBetweenEvictionRuns为空闲连接管理的线程执行的时间间隔

这个属性在设置时的代码如下:

public final void setTimeBetweenEvictionRuns(final Duration timeBetweenEvictionRuns) {
    this.durationBetweenEvictionRuns = PoolImplUtils.nonNull(timeBetweenEvictionRuns, BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS);
    startEvictor(this.durationBetweenEvictionRuns);
}

而在中启动销毁器的方法中,方法通过添加定时延迟任务实现了定时监控空闲连接并进行销毁的逻辑,这里定时任务的实现没有使用传统的springschedule等庞大的定时任务框架,而是使用了java自带的定时线程池ScheduledThreadPoolExecutor实现,这里不具体展开,有兴趣的同学可以自行百度了解。由定时线程池的创建参数可知,这里创建了只有一个定时线程的线程池,而定时线程执行的时间间隔则是我们设置的durationBetweenEvictionRuns

final void startEvictor(final Duration delay) {
    synchronized (evictionLock) {
        final boolean isPositiverDelay = PoolImplUtils.isPositive(delay);//判断设置的间隔时间不为0,-1,null等无效值
        if (evictor == null) { 
            if (isPositiverDelay) { 
                evictor = new Evictor();
                EvictionTimer.schedule(evictor, delay, delay);
            }
        } else if (isPositiverDelay) { 
            synchronized (EvictionTimer.class) {
                EvictionTimer.cancel(evictor, evictorShutdownTimeoutDuration, true);
                evictor = null;
                evictionIterator = null;
                evictor = new Evictor();
                EvictionTimer.schedule(evictor, delay, delay);
            }
        } else { // Stopping evictor
            EvictionTimer.cancel(evictor, evictorShutdownTimeoutDuration, false);
        }
    }
}

//EvictionTimer.schedule(evictor, delay, delay)
static synchronized void schedule(
        final BaseGenericObjectPool<?>.Evictor task, final Duration delay, final Duration period) {
        if (null == executor) {
            executor = new ScheduledThreadPoolExecutor(1, new EvictorThreadFactory());
            executor.setRemoveOnCancelPolicy(true);
            executor.scheduleAtFixedRate(new Reaper(), delay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
        }
        final WeakReference<Runnable> ref = new WeakReference<>(task);
        final WeakRunner runner = new WeakRunner(ref);
        final ScheduledFuture<?> scheduledFuture = executor.scheduleWithFixedDelay(runner, delay.toMillis(),
                period.toMillis(), TimeUnit.MILLISECONDS);
        task.setScheduledFuture(scheduledFuture);
        taskMap.put(ref, runner);
}

Evictor则是我们负责销毁空闲连接的守护线程,它的具体实现为:

public void run() {
    final ClassLoader savedClassLoader =
            Thread.currentThread().getContextClassLoader();
    try {
        ...
            
        try {
            evict();
        } catch(final Exception e) {
            swallowException(e);
        } catch(final OutOfMemoryError oome) {
            oome.printStackTrace(System.err);
        }
        try {
            ensureMinIdle();
        } catch (final Exception e) {
            swallowException(e);
        }
    } finally {
        // Restore the previous CCL
        Thread.currentThread().setContextClassLoader(savedClassLoader);
    }
}

他的操作主要有两个,清除空闲连接方法evict和保证连接池有足够空闲连接方法ensureMinIdle

清除空闲连接的方法主要做了两件事:回收过时的空闲连接,并验证未过时空闲连接,它的处理过程如下:

接下来看保证连接池有足够空闲连接方法,它主要负责当空闲连接小于最小空闲连接数时创建空闲连接:

在上数过程中,我们用到了哪些配置属性呢?

numTestsPerEvictionRun-每次检测空闲连接数

image-20221119161953912

这个是用来设置我们的定时任务每次检测多少个空闲连接是需要销毁和验证的,默认值为3. 即每次任务只验证3个空闲连接是否销毁和有效。

maxIdle&minIdle

是否创建空闲连接的判断条件,默认值都为8,如果默认取最小,如果最小空闲连接>最大空闲连接,则以最大空闲连接为准。所以若想调整空闲连接数则需要把两个属性值同时调大。

testWhileIdle-验证空闲连接

为什么要验证空闲连接,因为如果我们长时间不连接mysql,mysql也会有空闲连接等待时长,如果不验证,则会因为超过了mysql的空闲连接等待时长导致我们连接池中的空闲连接不可用,所以需要验证空闲连接。

image-20221119161656392

minEvictableIdleDuration&softMinEvictableIdleDuration

minEvictableIdleDuration为空闲连接保留时长,默认为30分钟。如果超过了这个时间,空闲连接就不会保留了。

softMinEvictableIdleDuration为空闲连接的软保留时长,默认为-1,即超过这个时间,但如果当前的空闲连接数还小于最小空闲连接数的话就还能继续保留。

image-20221119162322360

lifo-空闲队列是否为后进先出队列

这个在Evictor管理创建空闲连接时,决定了我们创建的连接时放入队首还是队尾,很好理解的一点是,由于我们获取连接时我们都是从队首获取,如果设为后进先出队列,则意味着我们把空闲连接队列变为空闲连接栈了,有的空闲连接创建了之后可能一直没有使用就被回收掉,因此这种方式在我们设置空闲连接最大生存时间时,不建议采用。

image-20221119161203101

对象连接池配置总结

属性 作用 默认值
maxTotal 连接池中允许的最大连接数量 -1,不设限制
blockWhenExhausted 连接耗尽时是否等待获取连接,如果阻塞为true则在连接池耗尽时,根据我们设置的maxWaitDuration来尝试获取连接;如果为false则直接抛出异常 true
maxWaitDuration 获取连接的最大等待时间 -1,不等待
lifo 空闲连接队列是否为后进先出队列 true
testOnCreate 连接创建后是否检测连接可用 false
testOnBorrow 连接使用前是否检测连接可用 false
testOnReturn 返回连接时是否检测连接可用 false
testWhileIdle 定时管理任务是否验证空闲连接 false
durationBetweenEvictionRuns 定时管理任务的执行时间间隔 -1,不执行
numTestsPerEvictionRun 定时管理任务每次检测的空闲连接数 3
minEvictableIdleDuration 空闲连接保留时长 30X60X1000
softMinEvictableIdleDuration 空闲连接的软保留时长 -1
evictionPolicy 空闲连接的销毁判断策略,可自定义

注意:验证sql和验证查询超时时间属于数据库连接属性,在创建数据库连接的工厂类中:

/**
 * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s.
 *
 * @since 2.0
 */
public class PoolableConnectionFactory implements PooledObjectFactory<PoolableConnection> {

	...
	private Collection<String> connectionInitSqls;

	private Collection<String> disconnectionSqlCodes;

    private Boolean defaultReadOnly;

    private Boolean defaultAutoCommit;

    private boolean autoCommitOnReturn = true;

    private boolean rollbackOnReturn = true;

    private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION;
    ...
}
0

评论区