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

南橘ryc發表於2021-01-11

一、連線池的定義

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

大家接觸最多的連線池、大概是資料庫連線或者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兩年半時間的南橘,下面是我的微信,需要之前的導圖或者想互相交流經驗的小夥伴可以一起互相交流哦。

相關文章