池化技术在真实业务中的实践(二)

前言

在上一篇分享(池化技术在真实业务中的实践)中,简单介绍了如何使用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;
    }
}
相关推荐
liu_chunhai2 分钟前
设计模式(3)builder
java·开发语言·设计模式
姜学迁10 分钟前
Rust-枚举
开发语言·后端·rust
ya888g38 分钟前
GESP C++四级样题卷
java·c++·算法
【D'accumulation】1 小时前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
小叶学C++1 小时前
【C++】类与对象(下)
java·开发语言·c++
2401_854391081 小时前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端
Cikiss1 小时前
微服务实战——SpringCache 整合 Redis
java·redis·后端·微服务
wxin_VXbishe1 小时前
springboot合肥师范学院实习实训管理系统-计算机毕业设计源码31290
java·spring boot·python·spring·servlet·django·php
Cikiss1 小时前
微服务实战——平台属性
java·数据库·后端·微服务
无敌の星仔1 小时前
一个月学会Java 第2天 认识类与对象
java·开发语言