目錄
🎂基礎知識
🚩整體內容
🌼單例模式創建
🎂連接池(代碼實現)
初始化
獲取 && 釋放連接
銷毀連接池
🍑RAII 機制釋放數據庫連接
定義
實現
🎂基礎知識
什么是數據庫連接池?
池是一組資源的集合,這組資源在服務器啟動之初,就被完全創建好并初始化
通俗來說,池是資源的容器,本質上是對資源的復用
顧名思義,連接池中的資源為一組數據庫連接,由程序動態的對池中的連接進行使用 / 釋放
當系統開始處理客戶請求時,如果它需要相關的資源,可以直接從池中獲取,無需動態分配;
當服務器處理完一個客戶連接后,可以把相關資源放回池中,無需執行系統調用釋放資源
數據庫訪問的一般流程是什么?
當系統需要訪問數據庫時,先系統創建數據庫連接,完成數據庫操作,然后系統斷開數據庫連接
為什么要創建連接池?
從一般流程看,若系統需要頻繁訪問數據庫,則需要頻繁創建和斷開數據庫連接,
而創建數據庫連接,是一個很耗時的操作,也容易對數據庫造成安全隱患
在程序初始化的時候,集中創建多個數據庫連接,并把它們集中管理,供程序使用,可以保證較快的數據庫讀寫速度,更加安全可靠
🚩整體內容
概述
池可以看作資源的容器,有多種實現方法:數組,鏈表,隊列...
這里,使用 單例模式 和 鏈表 創建數據庫連接池,實現對數據庫連接資源的復用
TinyWebserver 中,數據庫模塊分 2 部分:
1)數據庫連接池的定義
2)利用連接池完成登錄注冊的校驗功能
具體地,工作線程從數據庫連接池取得一個連接,訪問數據庫中的數據,訪問完畢后將連接交還連接池
內容
本博客介紹數據庫連接池的定義,具體涉及到:單例模式的創建,連接池代碼的實現,RAII 機制釋放數據庫連接
- 單例模式創建
描述連接池的單例實現- 連接池代碼實現
對連接池的外部外文接口的理解- RAII 機制釋放數據庫連接
描述連接釋放的封裝邏輯
🌼單例模式創建
使用局部靜態變量懶漢模式創建連接池
class connection_pool
{
public:// 局部靜態變量單例模式static connection_pool *GetInstance();private:connection_pool();~connection_pool();
};// 類外實現
connection_pool *connection_pool::GetInstance()
{static connection_pool connPool;return &connPool;
}
🎂連接池(代碼實現)
連接池的定義中注釋比較詳細,這里僅對其實現進行解析
連接池的功能:初始化,獲取連接,釋放連接,銷毀連接池
初始化
值得注意的是,銷毀連接池沒有直接被外部調用,而是通過 RAII 機制來完成自動釋放;
使用信號量實現多線程爭奪連接的同步機制,這里將信號量初始化為數據庫的連接總數
// 構造函數,初始化連接池中的連接數量
connection_pool::connection_pool()
{this->CurConn = 0; // 當前連接數this->FreeConn = 0; // 空閑連接數
}// 析構函數,銷毀連接池
connection_pool::~connection_pool()
{DestroyPool(); // 銷毀連接池
}// 初始化連接池
void connection_pool::init(string url, string User, string PassWord,string DBName, int Port, unsigned int MaxConn)
{// 初始化數據庫信息this->url = url; // 數據庫地址this->Port = Port; // 數據庫端口this->User = User; // 用戶名this->PassWord = PassWord; // 密碼this->DatabaseName = DBName; // 數據庫名稱// 創建 MaxConn 條數據庫連接for (int i = 0; i < MaxConn; i++) {MYSQL *con = NULL; // MySQL 連接指針con = mysql_init(con); // 初始化連接if (con == NULL) {cout << "Error:" << mysql_error(con); // 輸出錯誤信息exit(1); // 退出程序}con = mysql_real_connect(con, url.c_Str(), User.c_str(),DBName.c_str(), Port, NULL, 0); // 連接數據庫if (con == NULL) {cout << "Error: " << mysql_error(con); // 輸出錯誤信息exit(1); // 退出程序}// 更新連接池和空閑連接數量connList.push_back(con); // 將連接添加到連接池列表++FreeConn; // 空閑連接數加一}// 信號量初始化為最大連接數reserve = sem(FreeConn);this->MaxConn = FreeConn; // 最大連接數等于空閑連接數
}
獲取 && 釋放連接
當線程數量大于數據庫連接數量,使用信號量進行同步,每次取出連接,信號量原子 -1,釋放連接原子 +1;若連接池內沒有連接了,則阻塞等待
另外,由于多線程操作連接池,會造成競爭,這里用 互斥鎖 完成同步,具體的同步機制均使用 lock.h 中封裝好的類
// 當有請求時,從數據庫連接池返回一個可用連接,
// 更新使用和空閑連接數
MYSQL *connection_pool::GetConnection()
{MYSQL *con = NULL; // MySQL 連接指針if (0 == connList.size()) // 如果連接池為空,返回空指針return NULL;// 取出連接,信號量原子 -1,為 0 則等待reserve.wait(); // 等待信號量lock.lock(); // 加鎖con = connList.front(); // 獲取連接池中的第一個連接connList.pop_front(); // 彈出連接// 這里兩個變量,沒有用到,雞肋啊...--FreeConn; // 空閑連接數減一++CurConn; // 當前連接數加一lock.unlock(); // 解鎖return con; // 返回連接
}// 釋放當前使用的連接
bool connection_pool::ReleaseConnection(MYSQL *con)
{if (NULL == con) // 如果連接為空,返回falsereturn false;lock.lock(); // 加鎖connList.push_back(con); // 將連接放回連接池++FreeConn; // 空閑連接數加一--CurConn; // 當前連接數減一lock.unlock(); // 解鎖// 釋放連接原子 +1reserve.post(); // 釋放信號量return true; // 返回true
}
銷毀連接池
1)通過 迭代器 遍歷連接池鏈表
2)關閉對應數據庫連接
3)清空鏈表
4)并重置空閑連接和現有連接數量
// 銷毀數據庫連接池
void connection_pool::DestroyPool()
{lock.lock(); // 加鎖if (connList.size() > 0) { // 如果連接池不為空// 迭代器遍歷,關閉數據庫連接list<MYSQL *>::iterator it; // 聲明迭代器it,用于遍歷connList列表for (it = connList.begin(); it != connList.end(); ++it) // 遍歷連接池中的每個連接{MYSQL *con = *it; // 獲取迭代器指向的連接mysql_close(con); // 關閉數據庫連接}CurConn = 0; // 將當前連接數設置為0FreeConn = 0; // 將空閑連接數設置為0// 清空listconnList.clear(); // 清空連接池列表lock.unlock(); // 解鎖}// 無論是否進入 if 分支,都能正確釋放互斥鎖lock.unlock(); // 解鎖
}
🍑RAII 機制釋放數據庫連接
將數據庫連接的獲取與釋放通過 RAII 機制封裝,避免手動釋放
定義
需要注意的是,獲取連接時,通過有參構造對傳入的參數進行修改;
其中,數據庫連接本身是指針類型,所以參數需要通過雙指針才能對其進行修改
雙指針:指向指針的指針,避免暴露指針內部細節?
class connectionRAII {
public:// 雙指針對 MYSQL *con 修改connectionRAII(MYSQL **con, connection_pool *connPool); // 構造函數,傳入雙指針用于修改MYSQL *con~connectionRAII(); // 析構函數private:MYSQL *conRAII; // 數據庫連接指針connection_pool *poolRAII; // 連接池指針
};
實現
?不直接調用? 獲取和釋放連接的接口,將其封裝起來,通過 RAII 機制進行獲取和釋放
connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool)
{// 通過雙指針傳入的MYSQL **SQL,將其指向連接池中獲取的連接*SQL = connPool->GetConnection();// 將獲取到的連接賦值給conRAII數據庫連接指針conRAII = *SQL;// 將傳入的連接池指針賦值給poolRAIIpoolRAII = connPool;
}// 析構函數,用于釋放連接
connectionRAII::~connectionRAII()
{// 通過連接池指針釋放連接conRAIIpoolRAII->ReleaseConnection(conRAII);
}
?