天天看點

基于Apache元件,分析對象池原理

基于Apache元件,分析對象池原理

本文從對象池的一個簡單案例切入,主要分析common-pool2元件關于:池、工廠、配置、對象管理幾個角色的源碼邏輯,并且參考其在Redis中的實踐。

池塘裡養:Object;

一、設計與原理

1、基礎案例

首先看一個基于

common-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<DevObj> {
    @Override
    public DevObj create() throws Exception {
        // 建立對象
        return new DevObj() ;
    }
    @Override
    public PooledObject<DevObj> wrap(DevObj devObj) {
        // 池化對象
        return new DefaultPooledObject<>(devObj);
    }
}
/**
 * 對象池
 */
class DevObjPool extends GenericObjectPool<DevObj> {
    public DevObjPool() {
        super(new DevObjFactory(), new GenericObjectPoolConfig<>());
    }
}
           

案例中對象是完全自定義的;對象工廠中則重寫兩個核心方法:建立和包裝,以此建立池化對象;對象池的建構依賴定義的對象工廠,配置采用元件提供的正常配置類;可以通過調整對象執行個體化的時間以及建立對象的個數,初步了解對象池的原理。

2、接口設計

1.1 PooledObjectFactory 接口

  • 工廠類,負責對象執行個體化,建立、驗證、銷毀、狀态管理等;
  • 案例中

    BasePooledObjectFactory類

    則是該接口的基礎實作;

1.2 ObjectPool 接口

  • 對象池,并且繼承

    Closeable

    接口,管理對象生命周期,以及活躍和空閑對象的資料資訊擷取;
  • 案例中

    GenericObjectPool類

    是對于該接口的實作,并且是可配置化的方式;

1.3 PooledObject 接口

  • 池化對象,基于包裝類被維護在對象池中,并且維護一些附加資訊用來跟蹤,例如時間、狀态;
  • 案例中采用

    DefaultPooledObject

    包裝類,實作該接口并且線程安全,注意工廠類中的重寫;

3、運作原理

基于Apache元件,分析對象池原理

通過對象池擷取對象,可能是通過工廠新建立的,也可能是空閑的對象;當對象擷取成功且使用完成後,需要歸還對象;在案例執行過程中,不斷查詢對象池中空閑和活躍對象的數量,用來監控池的變化。

二、構造分析

1、對象池

public GenericObjectPool(final PooledObjectFactory<T> factory,final GenericObjectPoolConfig<T> config);
           

在完整的構造方法中,涉及到三個核心對象:工廠對象、配置對象、雙端阻塞隊列;通過這幾個對象建立一個新的對象池;在config中提供了一些簡單的預設配置:例如maxTotal、maxIdle、minIdle等,也可以擴充自定義配置;

2、雙端隊列

private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
public GenericObjectPool(final PooledObjectFactory<T> factory,final GenericObjectPoolConfig<T> config) {
    idleObjects = new LinkedBlockingDeque<>(config.getFairness());
}
           

LinkedBlockingDeque支援在隊列的首尾操作元素,例如添加和移除等;操作需要通過主鎖進行加鎖,并且基于兩個狀态鎖進行協作;

// 隊首節點
private transient LinkedBlockingDeque.Node<E> first;
// 隊尾節點
private transient LinkedBlockingDeque.Node<E> 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 ;
           
基于Apache元件,分析對象池原理

首先從隊列中擷取對象;如果沒有擷取到,調用工廠建立方法,之後池化管理;對象擷取之後會改變狀态為

ALLOCATED

使用中;最後經過工廠的确認,完成對象擷取動作;

3、歸還對象

public void GenericObjectPool.returnObject(final T obj) ;
           
基于Apache元件,分析對象池原理

歸還對象的時候,首先轉換為池化對象和标記

RETURNING

狀态;經過多次校驗判斷,如果失敗則銷毀該對象,并重新維護對象池中可用的空閑對象;最終對象被标記為空閑狀态,如果不超出最大空閑數,則對象被放到隊列的某一端;

4、對象狀态

關于池化對象的狀态在

PooledObjectState

類中有枚舉和描述,在圖中隻是對部分幾個狀态流轉做示意,更多細節可以參考狀态類;

基于Apache元件,分析對象池原理

可以參考在上述案例中使用到的

DefaultPooledObject

預設池化對象類中相關方法,結合狀态枚舉,可以了解不同狀态之間的校驗和轉換。

四、Redis應用

Lettuce作為Redis進階的用戶端元件,通信層使用Netty元件,并且是線程安全,支援同步和異步模式,支援叢集和哨兵模式;作為當下項目中常用的配置,其底層對象池基于

common-pool2

元件。

1、配置管理

基于如下配置即表示采用Lettuce元件,其中涉及到池的幾個參數配置:最小空閑、最大活躍、最大空閑;這裡可以對比GenericObjectPoolConfig中的配置:

spring:
  redis:
    host: ${REDIS_HOST:127.0.0.1}
    lettuce:
      pool:
        min-idle: 10
        max-active: 100
        max-idle: 100
           

2、源碼分析

圍繞對象池的特點,自然去追尋源碼中關于:配置、工廠、對象幾個核心的角色類;從上述配置參數切入,可以很容易發現如下幾個類:

基于Apache元件,分析對象池原理

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的相關參數,建構到GenericObjectPoolConfig類中,即配置加載過程;

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);
    }
}
           

在構造方法中擷取對象池的配置資訊,這裡并沒有直接執行個體化池對象,而是采用ConcurrentHashMap容器來動态維護;

2.3 對象管理

class LettucePoolingConnectionProvider implements LettuceConnectionProvider {
    // 擷取Redis連接配接
    public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> 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連接配接對象;釋放對象時首先判斷對象所屬的池,将對象歸還到相應的池中。

最後總結,本文從對象池的一個簡單案例切入,主要分析

common-pool2

元件關于:池、工廠、配置、對象管理幾個角色的源碼邏輯,并且參考其在Redis中的實踐,隻是冰山一角,像這種通用型并且應用範圍廣的元件,很值得時常去讀一讀源碼,真的令人驚歎其鬼斧天工的設計。

五、參考源碼

應用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent

元件封裝:
https://gitee.com/cicadasmile/butte-frame-parent
           
Gitee首頁:

https://gitee.com/cicadasmile/butte-java-note