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

前言

在上一篇分享(池化技术在真实业务中的实践)中,简单介绍了如何使用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;
    }
}
相关推荐
互联网全栈架构13 分钟前
遨游Spring AI:第一盘菜Hello World
java·人工智能·后端·spring
优秀的颜1 小时前
计算机基础知识(第五篇)
java·开发语言·分布式
BillKu1 小时前
Java严格模式withResolverStyle解析日期错误及解决方案
java
网安INF1 小时前
ElGamal加密算法:离散对数难题的安全基石
java·网络安全·密码学
AWS官方合作商2 小时前
在CSDN发布AWS Proton解决方案:实现云原生应用的标准化部署
java·云原生·aws
gadiaola3 小时前
【JVM】Java虚拟机(二)——垃圾回收
java·jvm
coderSong25686 小时前
Java高级 |【实验八】springboot 使用Websocket
java·spring boot·后端·websocket
Mr_Air_Boy7 小时前
SpringBoot使用dynamic配置多数据源时使用@Transactional事务在非primary的数据源上遇到的问题
java·spring boot·后端
豆沙沙包?7 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
年老体衰按不动键盘8 小时前
快速部署和启动Vue3项目
java·javascript·vue