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

前言

在上一篇分享(池化技术在真实业务中的实践)中,简单介绍了如何使用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;
    }
}
相关推荐
专注VB编程开发20年1 分钟前
VB.NET关于接口实现与简化设计的分析,封装其他类
java·前端·数据库
大数据魔法师24 分钟前
Redis(三) - 使用Java操作Redis详解
java·数据库·redis
天天爱吃肉821834 分钟前
车载以太网驱动智能化:域控架构设计与开发实践
java·运维·网络协议·微服务
IT光35 分钟前
Redis 五种类型基础操作(redis-cli + Spring Data Redis)
java·数据库·redis·spring·缓存
keke1036 分钟前
Java【14_3】接口(Comparable和Comparator)、内部类-示例
java·开发语言·servlet
言之。1 小时前
Go 语言中接口类型转换为具体类型
开发语言·后端·golang
代码不停1 小时前
Java二叉树题目练习
java·开发语言·数据结构
MaCa .BaKa1 小时前
38-日语学习小程序
java·vue.js·spring boot·学习·mysql·小程序·maven
贺函不是涵1 小时前
【沉浸式求职学习day41】【Servlet】
java·学习·servlet·maven
Excuse_lighttime1 小时前
JVM 机制
java·linux·jvm