前言
在上一篇分享(池化技术在真实业务中的实践)中,简单介绍了如何使用GenericObjectPool
快速实现一个自定义对象池。也贴了一份"常驻4个进程"的配置参数。
java
private GenericObjectPoolConfig<MyProcess> genericObjectPoolConfig() {
final GenericObjectPoolConfig<MyProcess> config = new GenericObjectPoolConfig<>();
config.setMaxTotal(20); // 池的最大容量
config.setMaxIdle(4); // 最大空闲连接数
config.setMinIdle(0); // 最小空闲连接数
config.setMaxWait(Duration.ofSeconds(5)); // 获取对象时最大等待时间
config.setTimeBetweenEvictionRuns(Duration.ofMinutes(1)); // 空闲对象检查间隔
config.setMinEvictableIdleTime(Duration.ofMinutes(10)); // 空闲对象被移除的最小空闲时间
config.setTestOnBorrow(true);
config.setLifo(false);
return config;
}
注意有坑
如果有人使用上述提供的参数去尝试运行,就会发现当对象池中的对象都空闲(idle)一段时间后,对象池里的空闲对象会被全部清理(evict),而不是保持"4个空闲对象"。当然原因也很简单,需要先了解下 GenericObjectPool
代码里使用MaxIdle、MinIdle这2个参数的地方
MaxIdle
就看下 GenericObjectPool 的 returnObject 方法。
java
@Override
public void returnObject(final T obj) {
final PooledObject<T> p = getPooledObject(obj);
// 省略不相关的代码...
final int maxIdleSave = getMaxIdle();
if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
try {
destroy(p, DestroyMode.NORMAL);
} catch (final Exception e) {
swallowException(e);
}
}
}
代码的第8行,if 条件会对比 maxIdle 和 对象池中空闲对象的数量;如果空闲数量大于maxIdle则将归还的对象直接销毁。也就是不放回对象池了。
MinIdle
则是在 GenericObjectPool 的 evict 方法。在贴代码前,需要先知道 GenericObjectPool 里有个叫Evictor
的内部类,它是个定时任务(TimerTask)的实现类,作用就是定时去检查对象池对象的状态,比如对象空闲的时间,是否要移除对象等等。 Evictor
的核心方法就是 evict
java
@Override
public void evict() throws Exception {
if (!idleObjects.isEmpty()) {
// 驱逐策略
final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();
synchronized (evictionLock) {
// 驱逐配置,看到了minIlde参数
final EvictionConfig evictionConfig = new EvictionConfig(
getMinEvictableIdleDuration(),
getSoftMinEvictableIdleDuration(),
getMinIdle());
for (int i = 0, m = getNumTests(); i < m; i++) {
// 省略不相关的代码...
boolean evict;
try {
// 重点!!
evict = evictionPolicy.evict(evictionConfig, underTest,
idleObjects.size());
} catch (final Throwable t) {
evict = false;
}
// 省略不相关的代码...
}
}
}
默认驱逐策略: 抛开idleduration检查,里面的重点就是 minIdle 和 idleCount 的对比。因为minIlde设置的是0, 所以只要有空闲对象(idle)达到空闲时间阈值,就会满足驱逐条件被驱逐。这也就解释了为什么没有常驻"4个空闲对象"
java
public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {
@Override
public boolean evict(final EvictionConfig config,
final PooledObject<T> underTest,
final int idleCount) {
// @formatter:off
return (config.getIdleSoftEvictDuration().compareTo(underTest.getIdleDuration()) < 0 &&
config.getMinIdle() < idleCount) ||
config.getIdleEvictDuration().compareTo(underTest.getIdleDuration()) < 0;
// @formatter:on
}
}
解决方案
尝试一:修改MinIdle值等于4
这样改确实能够避免4个空闲对象被定时任务驱逐。但是会产生一个新的问题:当对象池里有4个活跃对象(active)时,多余的空闲对象(idle)依然不会被驱逐。举个例子,对象池里现在有8个对象,4个是活跃对象,4个是空闲对象。我们的预期是这4个空闲对象,应该在一定时间后被驱逐。但 MinIdle 改成4后,就不会。
尝试二: 自定义驱逐策略
GenericObjectPoolConfig
对象池的配置类支持设置自定义驱逐策略。
java
private GenericObjectPoolConfig<RenderEngineProvider> genericObjectPoolConfig() {
// 自定义策略
customEvictionPolicy = new CustomEvictionPolicy();
config.setEvictionPolicy(customEvictionPolicy);
return config;
}
既然能够自定义,很容易实现一个符合预期效果的驱逐策略。比如:
java
public class CustomEvictionPolicy extends DefaultEvictionPolicy<Object> {
private int maxIdle;
public CustomEvictionPolicy(int maxIdle) {
this.maxIdle = maxIdle;
}
@Override
public boolean evict(final EvictionConfig config,
final PooledObject<Object> underTest,
final int idleCount) {
return super.evict(config, underTest, idleCount) && maxIdle < idleCount;
}
}