1. 程式人生 > 其它 >連線池原來這麼簡單(一分鐘系列)

連線池原來這麼簡單(一分鐘系列)

應網友要求,寫一寫連線池實現細節。

一、如何通過連線訪問下游

工程架構中有很多訪問下游的需求,下游包括但不限於服務/資料庫/快取,其通訊步驟是為:

(1)與下游建立一個連線

(2)通過這個連線,收發請求

(3)互動結束,關閉連線,釋放資源

這個連線是什麼呢,通過連線怎麼呼叫下游介面?服務/資料庫/快取,官方會提供不同語言的Driver、Document、DemoCode來教使用方建立連線與呼叫介面,以MongoDB的C++官方Driver API為例(虛擬碼):

DBClientConnection* c = new DBClientConnection();

c->connect(“127.0.0.1:8888”);

c->insert(“db.s”, BSON(”shenjian”));

c->close();

這個DBClientConnection就是一個與MongoDB的連線,官方Driver通過它提供了若干API,讓使用者可以對MongoDB進行連線,增刪查改,關閉的操作,從而實現不同的業務邏輯。

二、為什麼需要連線池

當併發量很低的時候,上述虛擬碼沒有任何問題,但當服務單機QPS達到幾百、幾千的時候,建立連線connect和銷燬連線close就會成為瓶頸,此時該如何優化?

結論也很簡單,服務啟動的時候,先建立好若干連線Array[DBClientConnection],當有請求過來的時候,從Array中取出一個,執行下游操作,執行完再放回,從而避免反覆的建立和銷燬連線,以提升效能。

這個對Array[DBClientConnection]進行維護的資料結構,就是連線池。有了連線池之後,資料庫操作的虛擬碼變為:

DBClientConnection* c = ConnectionPool::GetConnection();

c->insert(“db.s”, BSON(”shenjian”));

ConnectionPool::FreeConnection(c);

三、連線池核心介面與實現

通過上面的討論,可以看到連線池ConnectionPool主要有三個核心介面:

(1)Init:初始化好Array[DBClientConnection],這個介面只在服務啟動時呼叫一次

(2)GetConnection:請求每次需要訪問資料庫時,不是connect一個連線,而是通過連線池的這個介面來拿

(3)FreeConnection:請求每次訪問完資料庫時,不是close一個連線,而是把這個連線放回連線池

連線池核心資料結構:

(1)連線陣列Array DBClientConnection [N]

(2)互斥鎖陣列Array lock[N]

連線池核心介面實現:

Init(){
 for i = 1 to N {
  Array DBClientConnection [i] = new();
  Array DBClientConnection [i]->connect();
  Array lock[i] = 0;
 }
}

說明:把所有連線和互斥鎖初始化

GetConnection()
 for i = 1 to N {
  if(Array lock[i] == 0){
   Array lock[i] = 1;
   return Array DBClientConnection[i];
   }
 }
}

說明:找一個可用的連線,鎖住,並返回連線

FreeConnection(c)
 for i = 1 to N {
 if(Array DBClientConnection [i] == c){
   Array lock[i] = 0;
   }
  }
}

說明:找到連線,把鎖釋放

可以發現,簡單的連線池管理並不是很複雜,基本原理即如上所述。

四、未盡事宜

上述虛擬碼忽略了一些細節,在實現連線池中是需要考慮的:

(1)如果連線全部被佔用,是返回失敗,還是讓上游等待

(2)需要實施連線可用性檢測

(3)為了讓呼叫方更友好,可能還需要包裝一層DAO層,讓“連線”這個東西對呼叫方都是黑盒的

(4)通過freeArray,connectionMap可以讓取連線和放回連線都達到O(1)時間複雜度

(5)可以通過hash實現id序列化

(6)負載均衡、故障轉移、服務自動擴容都可以在這一層實現

希望這一分鐘大家有收穫。