最近真真实实用到了通用池化框架 commons-pool2,又学到了一些新的功能。也让自己对这个框架有了新的认识。但是框架提供的 API 是相对的简单的,针对一些特殊的场景还是需要自己将这些 API 功能进行一些组合。

下面分享一下我在使用过程中用到的功能拓展。这里使用经验是基于org.apache.commons.pool2.impl.GenericKeyedObjectPool,部分拓展功能已经在org.apache.commons.pool2.impl.GenericObjectPool,估计是前者在使用中情况比较复杂,统一方法调用并不能很好适应多种多样的使用场景。

先来复习一下:

需求

整个对象池的对象最大值是固定的,因为硬件资源限制。我又想保障更多的 KeyedPool 里面有对象存活。但是每个 KeyedPool 又得保障短时间内高频调用能力,因为创建一个对象消耗过多。所以创建完之后,不能立即销毁对象。

所以我设置了每个 KeyedPool 最小空闲是 0,最大空闲时 5,最大值是 10。遇到的问题就是,当 KeyedPool 被并发调用过,总会有很多空闲对象,占用相当多系统资源,虽然没有到达最大值,总归不是很好。

这里我理解设置一个最大空闲时间,我把最小空闲值改成 1,就能满足需求了,但是并没有找到相关的 API,所以就想着自己组装了一下现有的 API。

用到的 API

在 commons-pool2 中提供的 API 是不提供设置每一个 KeyedPool 的最大空闲时间,这就导致我在使用过程中,只有达到每个 KeyedPool 的最大空闲值或者触达整个 pools 的最大值才会被回收。但是在实际的使用场景中,我想保留每个 KeyedPool 最大一个空闲值。

这里我们就需要自己实现这个功能。首先我们需要知道当前对象池中的所有 pool 的状态。API 如下:

@Override
public int getNumIdle(final K key) {
    final ObjectDeque<T> objectDeque = poolMap.get(key);
    return objectDeque != null ? objectDeque.getIdleObjects().size() : 0;
}

这里可以获取当前 key 对应的 pool 的空闲值。这里我们还需要前置条件就是获取所有 key 的 API。如下:

@Override
public Map<String, Integer> getNumActivePerKey() {
    final HashMap<String, Integer> result = new HashMap<>();

    poolMap.entrySet().forEach(entry -> {
        if (entry != null) {
            final K key = entry.getKey();
            final ObjectDeque<T> objectDequeue = entry.getValue();
            if (key != null && objectDequeue != null) {
                result.put(key.toString(), Integer.valueOf(
                        objectDequeue.getAllObjects().size() -
                        objectDequeue.getIdleObjects().size()));
            }
        }
    });
    return result;
}

这里只能曲线救国一下了,通过获取所有的 KeyedPool 的 Map 遍历去获取所有的 keys。还有一个获取所有等待着的 Map 的 API:org.apache.commons.pool2.impl.GenericKeyedObjectPool#getNumWaitersByKey。或者使用获取所有信息的 API:org.apache.commons.pool2.impl.GenericKeyedObjectPool#listAllObjects

然后我们就需要一个主动销毁对象池中对象的方法。销毁对象的方法底层是调用的org.apache.commons.pool2.impl.GenericKeyedObjectPool#destroy,框架提供了两个清理的 API:

  1. org.apache.commons.pool2.impl.GenericKeyedObjectPool#clear(K),清空某个 KeyedPool
  2. org.apache.commons.pool2.impl.GenericKeyedObjectPool#invalidateObject(K, T),销毁某个对象

这里我用的是第二种,首先把这个对象借出来,然后销毁。当然也可以通过访问私有访问直接销毁反射访问和修改 private 变量

实现思路

实现部分代码涉及到业务的比较多,这里就不分享了,大概说一下思路。

第一种

使用定时任务,间隔最大空闲时间扫描,获取所有 KeyedPool 的空闲对象,将其中多余的对象,先 borrow 出来,再 destroy 掉。

第二种

改造org.apache.commons.pool2.BaseKeyedPooledObjectFactory实现方法,在com.market.controller.common.config.ParagonClientPool.FunTester#create的时候对每一个 KeyedPool 的第一个对象标记,并把数据存到 data 中。在com.market.controller.common.config.ParagonClientPool.FunTester#destroyObject的时候做个判断,并修改 data 中的值。

data 记录每一个 KeyedPool 的第一个创建对象以及状态,然后再通过定时任务扫描直接将非标记状态且过期的对象全部都使用com.market.controller.common.config.ParagonClientPool.FunTester#destroyObject方法销毁掉。

第三种

这种算是拓展了,后续有针对不同的 key 进行优先级划分,高优的 KeyedPool 允许 2 个空闲的对象来保障并发能力,对于非高优的 KeyedPool 允许超过最大空闲时间之后,空闲对象为零,腾出更多资源给高优的 KeyedPool。


↙↙↙阅读原文可查看相关链接,并与作者交流