养在池塘里:
一、设计与原理1、基本案例
首先来看一个基于-pool2对象池组件的应用案例,主要包括工厂类、对象池、对象三个核心角色,以及池化对象的使用过程:
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ObjPool {
public static void main(String[] args) throws Exception {
// 声明对象池
DevObjPool devObjPool = new DevObjPool() ;
// 池中借用对象
DevObj devObj = devObjPool.borrowObject();
System.out.println("Idle="+devObjPool.getNumIdle()+";Active="+devObjPool.getNumActive());
// 使用对象
devObj.devObjInfo();
// 归还给对象池
devObjPool.returnObject(devObj);
System.out.println("Idle="+devObjPool.getNumIdle()+";Active="+devObjPool.getNumActive());
// 查看对象池
System.out.println(devObjPool.listAllObjects());
}
}
/**
* 对象定义
*/
class DevObj {
private static final Logger logger = LoggerFactory.getLogger(DevObj.class) ;
public DevObj (){
logger.info("build...dev...obj");
}
public void devObjInfo (){
logger.info("dev...obj...info");
}
}
/**
* 对象工厂
*/
class DevObjFactory extends BasePooledObjectFactory {
@Override
public DevObj create() throws Exception {
// 创建对象
return new DevObj() ;
}
@Override
public PooledObject wrap(DevObj devObj) {
// 池化对象
return new DefaultPooledObject(devObj);
}
}
/**
* 对象池
*/
class DevObjPool extends GenericObjectPool {
public DevObjPool() {
super(new DevObjFactory(), new GenericObjectPoolConfig());
}
}
在这种情况下,对象是完全定制的;在对象工厂中,重写了两个核心方法:创建和打包创建池化对象;对象池的构建依赖于定义的对象工厂,配置采用组件提供的通用配置类;通过调整对象实例化的时间和创建的对象数量,可以初步了解对象池化的原理。
2、界面设计
1.1 个接口
1.2 接口
1.3 接口
3、运行原理
通过对象池获取的对象可能是通过工厂新创建的,也可能是空闲对象;当对象成功获取并使用后,需要返回对象;案例执行过程中,不断查询对象池中空闲和活动对象的数量。监控池更改的数量。
二、构造分析1、对象池
public GenericObjectPool(final PooledObjectFactory factory,final GenericObjectPoolConfig config);
在完整的构造方法中,涉及到三个核心对象:工厂对象、配置对象、双端阻塞队列;通过这些对象创建一个新的对象池;提供了一些简单的默认配置:等,也可以扩展自定义配置;
2、双端队列
private final LinkedBlockingDeque<PooledObject> idleObjects;
public GenericObjectPool(final PooledObjectFactory factory,final GenericObjectPoolConfig config) {
idleObjects = new LinkedBlockingDeque(config.getFairness());
}
支持队列首尾操作元素,如添加、移除等;操作需要通过主锁进行锁定,基于两个状态锁进行配合;
// 队首节点
private transient LinkedBlockingDeque.Node first;
// 队尾节点
private transient LinkedBlockingDeque.Node last;
// 主锁
private final InterruptibleReentrantLock lock;
// 非空状态锁
private final Condition notEmpty;
// 未满状态锁
private final Condition notFull;
链表和队列的特性在之前的文章中已经分别进行了分析。这里的源码在JDK容器中也很常见。我不会在这里重复它们。对象池的整个结构有了大概的轮廓之后,我们再仔细看看管理逻辑。
三、对象管理1、添加对象
新建对象并放入池中,一般用于需要预加载的场景;它涉及两个核心操作:对象的工厂创建、对象池管理;
public void GenericObjectPool.addObject() throws Exception ;
2、借来的对象
public T GenericObjectPool.borrowObject(final long borrowMaxWaitMillis) throws Exception ;
首先从队列中获取对象;如果没有获取,则调用工厂创建方法,然后进行池管理;获取对象后,状态变为使用中;最后,工厂确认后,对象获取动作完成;
3、返回对象
public void GenericObjectPool.returnObject(final T obj) ;
返回对象时,首先转换为池化对象并标记状态;经过多次验证和判断,如果失败则销毁该对象,重新维护对象池中可用的空闲对象;最终对象将被标记为空闲状态,如果没有超过最大空闲数,则将该对象放在队列的一端;
4、对象状态
池化对象的状态在类中被枚举和描述。在图中,仅说明了几个状态流。更多细节请参考状态类;
可以参考上述案例中使用的默认池化对象类中的相关方法,结合状态枚举,可以了解不同状态之间的校验和转换。
四、Redis 应用
作为Redis的高级客户端组件,通信层使用Netty组件,线程安全,支持同步和异步模式,支持集群和哨兵模式;作为当前项目中常用的配置,底层对象池是基于-pool2组件的。
1、配置管理
基于下面的配置,就是采用组件,这涉及到pool的几个参数配置: idle、 、 idle;在这里你可以比较一下图中的配置:
spring:
redis:
host: ${REDIS_HOST:127.0.0.1}
lettuce:
pool:
min-idle: 10
max-active: 100
max-idle: 100
2、源码分析
着眼于对象池的特性,自然会在源码中寻找核心角色类:配置、工厂、对象;从上面的配置参数中,很容易找到以下几个类:
2.1 配置转换
// 连接配置
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
private static class PoolBuilderFactory {
// 构建对象池配置
private GenericObjectPoolConfig getPoolConfig(RedisProperties.Pool properties) {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(properties.getMaxActive());
config.setMaxIdle(properties.getMaxIdle());
config.setMinIdle(properties.getMinIdle());
return config;
}
}
}
这里将配置文件中Redis的相关参数内置到fig类中,即配置加载过程;
2.2 对象池构建
class LettucePoolingConnectionProvider implements LettuceConnectionProvider {
// 对象池核心角色
private final GenericObjectPoolConfig poolConfig;
private final BoundedPoolConfig asyncPoolConfig;
private final Map<Class, GenericObjectPool> pools = new ConcurrentHashMap(32);
LettucePoolingConnectionProvider(LettuceConnectionProvider provider, LettucePoolingClientConfiguration config) {
this.poolConfig = clientConfiguration.getPoolConfig();
this.asyncPoolConfig = CommonsPool2ConfigConverter.bounded(this.config);
}
}
在构造方法中获取对象池的配置信息。这里,池对象不是直接实例化的,而是使用容器进行动态维护的;
2.3 对象管理
class LettucePoolingConnectionProvider implements LettuceConnectionProvider {
// 获取Redis连接
public <T extends StatefulConnection> T getConnection(Class connectionType) {
GenericObjectPool pool = (GenericObjectPool)this.pools.computeIfAbsent();
StatefulConnection connection = (StatefulConnection)pool.borrowObject();
}
// 释放Redis连接
public void release(StatefulConnection connection) {
GenericObjectPool<StatefulConnection> pool = (GenericObjectPool)this.poolRef.remove(connection);
}
}
获取池对象时,如果不存在,则根据相关配置创建池对象,在Map容器中维护,然后从池中借用Redis连接对象;释放对象时,首先确定对象所属的池,将对象返回到对应的池中间。
综上,本文从一个简单的对象池案例入手,主要分析-pool2组件的源码逻辑:池、工厂、配置、对象管理角色,并参考其在Redis中的实践,只是冰山一角, 像这样它是一个通用且广泛使用的组件。不时阅读源代码是值得的。它的巧妙设计真是令人惊叹。
五、参考源码
应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent
组件封装:
https://gitee.com/cicadasmile/butte-frame-parent
暂无评论内容