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

前言

在上一篇分享(池化技术在真实业务中的实践)中,简单介绍了如何使用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;
    }
}
相关推荐
GoldenaArcher几秒前
GraphQL 工程化篇 III:引入 Prisma 与数据库接入
数据库·后端·graphql
西西学代码1 分钟前
Flutter---showCupertinoDialog
java·前端·flutter
多多*4 分钟前
上传文件相关业务,采用策略模式+模版方法模式进行动态解耦
java·开发语言
晨非辰5 分钟前
【面试高频数据结构(四)】--《从单链到双链的进阶,读懂“双向奔赴”的算法之美与效率权衡》
java·数据结构·c++·人工智能·算法·机器学习·面试
沐雨橙风ιε11 分钟前
Spring Boot整合Apache Shiro权限认证框架(实战篇)
java·spring boot·后端·apache shiro
左师佑图16 分钟前
Apache POI SXSSFWorkbook 报错“没有那个文件或目录”问题排查与解决方案
java·apache·excel
桦说编程22 分钟前
CompletableFuture 异常处理常见陷阱——非预期的同步异常
后端·性能优化·函数式编程
凸头24 分钟前
以AtomicInteger为例的Atomic 类的底层CAS细节理解
java·jvm·算法
艾派森27 分钟前
基于 Rokid CXR-M SDK 构建 AR 远程专家协作系统:从零实现眼镜端自定义 UI 与实时交互
java
李广坤32 分钟前
Springboot解决跨域的五种方式
后端