基於Apache-Commons-Pool2實現Grpc客戶端鏈接池

概述

在項目運行過程當中,有些操做對系統資源消耗較大,好比創建數據庫鏈接、創建Redis鏈接等操做,咱們但願一次性建立多個鏈接對象,並在之後須要使用時能直接使用已建立好的鏈接,達到提升性能的目的。池技術經過提早將一些佔用較多資源的對象初始化,並將初始化後的對象保存到池中備用,達到提升應用服務性能的目的,數據庫的JDBC鏈接池和Jedis鏈接池等都使用了池技術。
Apache-Commons-Pool2提供了一套池技術的規範接口和實現的通用邏輯,咱們只須要實現其抽象出來的方法就能夠了。這篇博文主要分享基於Apache-Commons-Pool2來實現Grpc鏈接池的應用。
關於Grpc相關的內容,你們如想了解基本的實現方法,能夠參考個人另外一篇博客(傳送門):http://www.javashuo.com/article/p-epfzshdk-hz.html數據庫

核心組件

咱們先來了解一下Apache-Commons-Pool2規範接口中涉及到的幾個核心組件,包括:apache

  • ObjectPool
    對象池,用於存儲對象,並管理對象的入池和出池。對象池的實現類是 GenericObjectPool<T>;
  • PoolConfig
    池屬性,用於設置鏈接池的一些配置信息,好比最大池容量、超過池容量後的處理邏輯等。池屬性的實現類是:GenericObjectPoolConfig;
  • ObjectFactory
    對象工廠,在須要的時候生成新的對象實例,並放入池中。對象工廠的接口是:interface PooledObjectFactory<T>;
  • ClientObject
    池對象,由對象工廠負責建立,並放入到對象池中;須要使用時從對象池中取出,執行對象的業務邏輯,使用完後再放回對象池。池對象的接口是:interface PooledObject<T>。

    核心組件依賴關係及其工做流程

    接口與類之間的依賴關係

    在梳理鏈接池相關的核心組件工做流程以前,咱們先來了解一下核心組件涉及到的類和接口之間的繼承和實現關係。架構

  • 對象池類的繼承關係
    對象池的最頂層接口是ObjectPool<T>,裏面定義了對象池的基本方法,包括對象的添加、取出、校驗、返還,以及獲取處於Idle休眠狀態的對象數量、獲取處於Active狀態的對象數量、清空池、關閉池。
    抽象類BaseGenericObjectPool<T>,定義了對象池的初始配置,並實現了對象池的基本接口方法。
    池類GenericObjectPool<T>繼承了抽象類BaseGenericObjectPool<T>,並實現了ObjectPool<T>接口。其中添加了對象工廠、存儲全部對象的Map、存儲Idle對象的鏈式阻塞隊列、當前已建立的對象數等屬性。
    因爲GenericObjectPool<T>類支持範型,咱們要作的,就是指定GenericObjectPool<T>池類返回的池對象類型<T>,並設置對象工廠類、配置類等池屬性;或者繼承GenericObjectPool類以添加更多的自定義池特性。
    基於Apache-Commons-Pool2實現Grpc客戶端鏈接池併發

  • 池屬性類的繼承關係
    池屬性的最上層接口是interface Cloneable,抽象類BaseObjectPoolConfig實現了這個接口,並定義了默認的池配置屬性。
    GenericObjectPoolConfig類繼承了BaseObjectPoolConfig,一樣定義了默認的池配置屬性值。
    咱們能夠直接使用GenericObjectPoolConfig類,或者繼承GenericObjectPoolConfig類,根據本身的需求設置自定義池配置屬性。
    基於Apache-Commons-Pool2實現Grpc客戶端鏈接池ide

  • 池內對象類的繼承關係
    池內對象類實現了上層的PooledObject<T>接口,這個接口裏面定義了一個池對象須要實現的各類方法。
    另外,池內對象類還須要定義類自己須要具有的成員屬性和須要實現的業務方法。
    基於Apache-Commons-Pool2實現Grpc客戶端鏈接池性能

  • 對象工廠類的繼承關係
    對象工廠類實現了最上層的PooledObjectFactory<T>接口,該接口定義了對象工廠的核心功能方法,包括:建立對象、銷燬對象、校驗對象、激活對象、鈍化對象。
    基於Apache-Commons-Pool2實現Grpc客戶端鏈接池

工做流程

根據上述對核心組件的類繼承關係分析,咱們能夠梳理出一個流程,逐步實現各個組件,並組合成一套適用於咱們業務的鏈接池架構。咱們來看看這個流程該如何定義。
(1)定義咱們的池內對象類 ClientObject,並結合咱們的實際業務來實現上層接口的方法。
(2)定義對象工廠類ClientFactory,並結合咱們的實際業務來實現上層接口的方法。
(3)定義池屬性類ClientPoolConfig,結合咱們的實際需求來設置屬性值。
(4)使用對象池GenericObjectPool,指定泛型類型GenericObjectPool<ClientObject>。測試

基於Apache-Commons-Pool2實現Grpc客戶端鏈接池

鏈接池內部的核心業務邏輯:
基於Apache-Commons-Pool2實現Grpc客戶端鏈接池線程

池內對象的建立和返回邏輯是池技術裏的關鍵,能夠查看池對象的borrowObject方法去了解這部分細節內容。code

應用實踐

代碼實現

根據上述對Apache-commons-pool2的特色和實現流程的分析,咱們基於Grpc客戶端鏈接池的應用場景,來進行代碼實踐,主要包括實現池內對象類 ClientObject和實現對象工廠類ClientFactory。
具體代碼能夠進入個人百度網盤下載,連接以下:
https://pan.baidu.com/s/1eaGpz6XN2a3ssw0eYsNLww對象

代碼測試

爲了驗證咱們的Grpc鏈接池的做用,我編寫了一個測試方法,模擬如下場景,即開啓10個線程,每一個線程循環10次使用Grpc鏈接發送消息給grpc服務端,而後查看線程池中累計建立的鏈接對象個數、線程池中每一個鏈接對象的被使用次數等信息。
經過測試輸出的信息,我獲得的結論是:不使用鏈接池時,總共須要進行100次Grpc鏈接併發送消息;使用鏈接池後,總共僅須要創建2次Grpc鏈接來發送100次消息,每一個鏈接被調用了50次。
測試代碼以下。

package com.cmcc.littlec.grpc.poolclient;
import com.cmcc.littlec.grpc.util.Constants;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class test {
    @SuppressWarnings("unchecked")
    public static GenericObjectPool<ClientObject> getClientPool(){
        ClientPoolFactory factory = new ClientPoolFactory(Constants.grpcHost, Constants.grpcPort);
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxIdle(8);
        config.setMinIdle(3);
        config.setMaxTotal(18);
        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);
        GenericObjectPool<ClientObject> clientPool = new GenericObjectPool<ClientObject>(factory, config);
        return clientPool;
    }

    public static void  main(String[] args ){
        final GenericObjectPool<ClientObject> clientPool = getClientPool();
        for(int i=0; i<10; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10; i++) {
                            ClientObject client = clientPool.borrowObject();                                                        
                            String str = "hello, grpc client_" + i;//參數
                            try {
                                client.sayHello(str);
                            }catch(Exception e){
                                client.invalidate();
                            }
                            System.out.println("Thread : " + Thread.currentThread().getName() + "; clientPool size : " + clientPool.getCreatedCount());
                            System.out.println("clientObj : "+client.toString());
                            clientPool.returnObject(client);
                        }
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            });
            t.start();
            try {
                if(i%2==0) {
                    Thread.sleep(5000L);//每隔兩個線程建立後停頓5S
                }
            }catch(Exception e){ }
        }
    }
}
相關文章
相關標籤/搜索