一、連線池的定義
什麼叫連線池?顧名思義,連線池就是將應用所需的連線物件放在池中,每次訪問時從池中獲取,使用完畢再放回池中,以達到連線複用的目的。連線池和執行緒池很像,都是為了減少連線物件在建立、銷燬連線過程中不必要消耗的資源。
大家接觸最多的連線池、大概是資料庫連線或者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兩年半時間的南橘,下面是我的微信,需要之前的導圖或者想互相交流經驗的小夥伴可以一起互相交流哦。