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


