【進階之路】和南橘一起探索連接池(一)

一、連接池的定義

什麼叫連接池?顧名思義,連接池就是將應用所需的連接對象放在池中,每次訪問時從池中獲取,使用完畢再放回池中,以達到連接復用的目的。連接池和執行緒池很像,都是為了減少連接對象在創建、銷毀連接過程中不必要消耗的資源

大家接觸最多的連接池、大概是資料庫連接或者tomcat連接池,C3P0、DBCP、Tomcat Jdbc Pool、BoneCP、Druid等。這些連接池的目的都非常的純粹,即在服務啟動的時候,預先生成若干條連接,每當有請求過來,就從中取出一個,執行操作,執行完成後再放回,從而避免反覆的建立和銷毀連接,以提升性能

連接池性能對比

實際在微服務中,連接池是非常重要的組件,因為服務間需要建立連接通訊,通過連接池可以極大地提高服務間的通訊性能。

一般連接池的實現,圖片來自拉鉤教育

因此,我們只要建立多條連接,用一個數組維護多條連接就行了;如果使用一條連接,那麼從數組裡拿出這條連接,使用完再放入數組即可。當數組為空時,只要建立新的連接就可以了。

二、自定義連接池

具體的實現可以參考我下文實現的程式碼。這個連接池擁有了幾個基本數據。

  • maxIdleConns 最大空閑連接數,這個值相當於執行緒池裡的核心執行緒數、與執行緒池不一樣的是,配置了最大空閑數後,連接池的連接將會長期保持。這個值的設置很有講究,需要結合後端服務的連接承載能力設置

  • currentCount 當前使用數目,類似於核心執行緒數到最短執行緒數之前的這個值。

  • maxIdCount 最大連接數,代表著連接池的上限。

  • ttls 連接的過期時間,這個時間執行緒池中也有,如果連接都為空閑連接,則不會進行過期處理。

public class MyPool {

    // 最大連接數 
    private int maxIdCount;
    //記錄當前使用 的連接數目
    private int currentCount ;
    //初始化執行緒數、最大空閑連接數
    private int maxIdleConns;
	// 多久後連接會斷開,很多連接池會在一定時間後斷開連接,然後將新鮮的連接放入連接池
    private int ttls;

    //我們用LinkedList來定義一個連接池
    private LinkedList<Connections> connectionPool;

    public MyPool(int maxIdleConns, int currentCount,int maxIdCount,int ttls) {
        this.maxIdleConns = maxIdleConns;
        this.currentCount = currentCount;
        this.maxIdCount =maxIdCount;
        this.ttls=ttls;
        this.connectionPool = new LinkedList<Connections>();

        for(int i=0;i<maxIdleConns;i++){
            connectionPool.add(createConnection());
        }
    }
    /**
     * 建立一個鏈接
     * @return  Connection
     */
    public    Connections createConnection(){
        try {

            Class.forName("com.mysql.jdbc.Driver");
            return new Connections(DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root"), DateUtil.getCurrTime());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 先判斷連接池中是否有連接,如果有直接使用,如果沒有連接,
     * 則先判斷當前的連接是否達到最大連接數,達到的話 拋出異常,沒有達到最大連接 則創建連接
     * @return
     */
    public Connections getConnection(){
        //1.判斷,池中若有連接直接使用
        if(connectionPool.size()>0){
            //把這個鏈接移出集合併返回當前連接對象。
            currentCount++;
            return connectionPool.removeFirst();

        }
        //如果池中沒有連接而且沒有達到最大連接數目;則創建連接
        if(currentCount>=maxIdleConns && currentCount<maxIdCount){
            currentCount++;
            //創建一個新的連接
            return createConnection();
        }
        //判斷是否達到最大連接數,達到則拋出異常
        System.out.println();
        throw new RuntimeException("當前連接已經達到最大連接數!");
    }


    /**
     * 把連接放回連接池(集合)中。如果池中連接數小初始化連接數目就放回池中,其他則關閉連接。
     * @param conn
     */
    public void releaseConnection(Connections conn){
        //判斷池中的數目如果小於初始化連接就放回連接池中
        //判斷連接池中的剩餘數目是否<連接池初始化數目   如果為真 則放回連接池
        if(currentCount<=maxIdCount){
            //放回連接池
            connectionPool.addLast(conn);
            //當前連接-1
            currentCount--;

        }else{
            //關閉連接
            try {
                conn.connection.close();
                currentCount--;
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 清除全部超時連接
     * 初始化執行緒數無需釋放
     * @param connectionPool
     */
    public void release(LinkedList<Connections> connectionPool){
        if (connectionPool.size()<1||currentCount==0){
            return;
        }
        if ( ttls<(DateUtil.getCurrTime()-connectionPool.getFirst().getTtl())){
            Connections conn = connectionPool.getFirst();
            //當前連接-1
            currentCount--;
            connectionPool.removeFirst();
            //關閉連接
            try {
                conn.connection.close();
                currentCount--;
            } catch (SQLException e) {
                e.printStackTrace();
            }
            release(connectionPool);
        }
    }
}

三、連接池需要的問題

1、並發問題

上面的程式碼只是我簡單實現了一下連接池,但是為了使連接管理服務具有最大的通用性,必須考慮多執行緒環境,即並發問題。在java中,有各種鎖的機制能夠解決連接池的並發問題。

2、連接池大小的設置

如果設置得太大,假如設置 1000,始發集群有 100 台機器,那麼就會建立 10w 的持久連接,這對後端服務的壓力可想而知。但也不能設置得太小。

如果連接池滿了,就會建立新的連接,不斷建立的新連接會耗光後端服務的資源。

新建立的連接在用完之後,有兩種選擇——連接池有餘量的情況會放入連接池,反之會直接丟棄,這種情況在瞬間很容易出現,連接池持續瞬間被空閑連接佔滿(最大空閑連接數的叫法也由此得來),導致新連接無法放回連接池,進而丟棄,這樣就會形成建立連接—用完丟棄的惡性循環,連接池的作用也就消失了。對於HTTP請求的連接池來說,所有的連接都退化成了短連接。

實際上連接並沒有長短之分,只是取決於傳輸完數據後是否斷開。那麼為什麼會有長短連接的叫法呢?這是因為 HTTP 協議多用於 Web 中,Web 的交互方式多是一來一回的模式,這樣的應用場景下,不需要服務端推數據,所以建立連接後立即釋放也是完全可以的。

3、連接池的分配與釋放

連接池的分配與釋放,對系統的性能有很大的影響。合理的分配與釋放,可以提高連接的復用度,從而降低建立新連接的開銷,同時還可以加快用戶的訪問速度。
我們這個連接池是使用LinkedList來實現的,主要的目的是考慮到過期時間。在鏈表中,前面連接的持續時間一定高於後面的連接,也可以減少連接的輪循時間。

大家好,我是練習java兩年半時間的南橘,下面是我的微信,需要之前的導圖或者想互相交流經驗的小夥伴可以一起互相交流哦。