通用池化框架實踐之GenericKeyedObjectPool

前兩天寫了一篇文章介紹commons-pool2這個通用池化框架通用池化框架commons-pool2實踐,其中提到了可以池化一個對象和一組對象,一個對象用到了GenericObjectPool這個類,一組對象用到了GenericKeyedObjectPool這個類。

一開始我以爲後者比較複雜,所以放棄了嘗試,今天在寫gRPC接口測試Demo,根據分片不同進行負載均衡連接不同節點的過程中,遇到了一個障礙。就是在服務調用gRPC的時候已經完成了自動負載均衡,我調用的SDK就需要自己實現根據不同分片連接不同的節點,這就用到了GenericKeyedObjectPool

顧名思義,鍵值對象池。就是通過一個key對應一個對象類型來組合對象池,其本質上就是一個Mapkey是自定義,value就是org.apache.commons.pool2.ObjectPool,而但對象池化類GenericObjectPool也是實現了這個接口。

經過查詢源碼註釋有兩點需要注意:

  1. Map的用的ConcurrentHashMap,是線程安全的。
  2. 獲取和回收太頻繁,會遇到性能問題。

關於第二點,我有機會在做一期兩者的性能測試。我現在用的是gRPC的連接對象io.grpc.ManagedChannel,而且每個類對象綁定的對象是io.grpc.stub.AbstractBlockingStub並不會場景去連接池中獲取新連接,一個gRPC連接可以支撐N(資料稱該值100左右,後續我計劃50個線程公用一個連接)個線程的併發,所以暫時不用擔心這個性能問題。

根據上次文章的記錄的順序分成了三部分。

可池化類

首先我們需要一個可以被池化的對象,代碼同上期文章。

池化工廠類

然後就是池化工廠類,這個類需要定義keyvalue的類型,然後就是照葫蘆畫瓢,跟上期文章一樣。

package com.funtester.funpool

import com.funtester.base.interfaces.IPooled
import org.apache.commons.pool2.BaseKeyedPooledObjectFactory
import org.apache.commons.pool2.PooledObject
/**
 * 可池化工廠類
 */
abstract class KeyPoolFactory<F> extends BaseKeyedPooledObjectFactory<F, IPooled> {

    abstract IPooled init()

    @Override
    IPooled create(F k) throws Exception {
        return init()
    }

    @Override
    PooledObject<IPooled> wrap(IPooled obj) {
        return obj.reInit()
    }

    @Override
    void destroyObject(F key, PooledObject<IPooled> p) throws Exception {
        p.getObject().destory()
        super.destroyObject(key, p)
    }
}

這裏提一嘴,com.funtester.funpool.KeyPoolFactory#destroyObject方法並不是必需的,如果池化的對象除了內存以外不需要額外的資源釋放,就不用重寫這個方法了。還有一種情況就是對象信息需要清除,比如org.apache.http.client.methods.HttpGet,需要把請求地址和請求頭等信息清除,這個需要跟業務需求保持一致。不一定是全都清除。

對象池

照貓畫虎,定義屬性類型、配置項等等。

package com.funtester.funpool

import com.funtester.base.interfaces.IPooled
import org.apache.commons.pool2.impl.GenericKeyedObjectPool
import org.apache.commons.pool2.impl.GenericObjectPoolConfig

class KeyPool {

    KeyPool(KeyPoolFactory factory) {
        this.factory = factory
        this.pool = init()
    }

    private GenericKeyedObjectPool<String, IPooled> pool = init();

    private KeyPoolFactory<String> factory

    private GenericKeyedObjectPool<String, IPooled> init() {
        // 連接池的配置
        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        // 池中的最大連接數
        poolConfig.setMaxTotal(8);
        // 最少的空閒連接數
        poolConfig.setMinIdle(0);
        // 最多的空閒連接數
        poolConfig.setMaxIdle(8);
        // 當連接池資源耗盡時,調用者最大阻塞的時間,超時時拋出異常 單位:毫秒數
        poolConfig.setMaxWaitMillis(-1);
        // 連接池存放池化對象方式,true放在空閒隊列最前面,false放在空閒隊列最後
        poolConfig.setLifo(true);
        // 連接空閒的最小時間,達到此值後空閒連接可能會被移除,默認即爲30分鐘
        poolConfig.setMinEvictableIdleTimeMillis(1000L * 60L * 30L);
        // 連接耗盡時是否阻塞,默認爲true
        poolConfig.setBlockWhenExhausted(true);
        // 連接池創建
        return new GenericKeyedObjectPool<String, IPooled>(factory, poolConfig);
    }

}


然後我們就可以使用這個對象池了,我定義了兩個方法來演示兩種常見的場景:


    /**
     * 獲取對象
     */
    IPooled get(String key) {
        try {
            return pool.borrowObject("FunTester");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory.create("FunTester");
    }

    /**
     * 歸還對象
     * @param iPooled
     */
    void back(String key, IPooled iPooled) {
        pool.returnObject("FunTester", iPooled)
    }

    /**
     * 執行器
     */
    def execute(String key, Closure closure) {
        IPooled client = get(key);
        try {
            closure(client);
        } finally {
            back(key, client);
        }
    }


後續會放棄這種泛型的方式,因爲泛型更加麻煩。等我再學習幾天,再來測試這兩個池化類的性能,爲以後的使用提供參考依據。

Have Fun ~ Tester !

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章