最近真真实实用到了通用池化框架commons-pool2,又学到了一些新的功能。也让自己对这个框架有了新的认识。但是框架提供的API是相对的简单的,针对一些特殊的场景还是需要自己将这些API功能进行一些组合。
下面分享一下我在使用过程中用到的功能拓展。这里使用经验是基于org.apache.commons.pool2.impl.GenericKeyedObjectPool,部分拓展功能已经在org.apache.commons.pool2.impl.GenericObjectPool,估计是前者在使用中情况比较复杂,统一方法调用并不能很好适应多种多样的使用场景。
先来复习一下:
- 通用池化框架commons-pool2基础实践
- 通用池化框架GenericKeyedObjectPool性能测试
- 通用池化框架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:
- org.apache.commons.pool2.impl.GenericKeyedObjectPool#clear(K),清空某个KeyedPool
- 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。
FunTester原创专题推荐~ 2021年原创合集2022年原创合集接口功能测试专题性能测试专题Groovy专题Java、Groovy、Go、Python单测&白盒FunTester社群风采测试理论鸡汤FunTester视频专题案例分享:方案、BUG、爬虫UI自动化专题测试工具专题 — By FunTester