基于Apache组件,分析对象池原理

养在池塘里:

一、设计与原理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());

图片[1]-基于Apache组件,分析对象池原理-唐朝资源网

} }

在这种情况下,对象是完全定制的;在对象工厂中,重写了两个核心方法:创建和打包创建池化对象;对象池的构建依赖于定义的对象工厂,配置采用组件提供的通用配置类;通过调整对象实例化的时间和创建的对象数量,可以初步了解对象池化的原理。

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

© 版权声明
THE END
喜欢就支持一下吧
点赞200赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容