文章目錄
- 數據庫連接池是什么?
- Web Server 中為什么需要數據庫連接池?
- SqlConnectPool 成員變量
- 實現 Init() 函數
- 實現 ClosePool() 函數
- SqlConnectRAII 類
- SqlConnectPool 代碼
- SqlConnectPool 測試
從零開始實現 C++ TinyWebServer 項目總覽
項目源碼
數據庫連接池是什么?
前面實現了線程池,而數據庫連接池的思想與線程池類似,都是將數據庫連接或線程并存儲在一個 “池”(容器)中,便于后續使用。
數據庫連接池是一種數據庫連接管理技術,它預先創建一定數量的數據庫連接并存儲在一個 “池” 中。當應用程序需要與數據庫進行交互時,它不是直接創建新的數據庫連接,而是從連接池中獲取一個已經建立好的連接;當應用程序使用完連接后,將連接歸還給連接池,而不是直接關閉連接。這樣,連接可以被重復使用,避免了頻繁創建和銷毀連接帶來的開銷。
數據庫連接池的作用
- 減少資源消耗:創建和銷毀數據庫連接是一個相對昂貴的操作,涉及到網絡通信、身份驗證、資源分配等多個步驟。使用連接池可以復用已經創建好的連接,減少了頻繁創建和銷毀連接所帶來的系統資源消耗,提高了系統的性能。
- 提高響應速度:由于連接池中的連接已經預先創建好,應用程序可以直接從連接池中獲取連接,無需等待新連接的創建過程,從而顯著提高了應用程序的響應速度。
- 統一管理連接:連接池可以對連接進行統一管理,可以有效地控制數據庫連接的使用,避免因連接過多導致數據庫服務器資源耗盡。
- 增強系統的穩定性:連接池可以監控連接的狀態,當連接出現異常時,可以及時進行處理,如重新建立連接等。這有助于增強系統的穩定性,減少因連接問題導致的系統故障。
Web Server 中為什么需要數據庫連接池?
在 Web Server 中,通常會面臨大量的并發請求,每個請求可能都需要與數據庫進行交互。如果沒有數據庫連接池,每次請求都要創建一個新的數據庫連接,會導致以下問題:
- 性能瓶頸:頻繁創建和銷毀數據庫連接會消耗大量的系統資源,導致 Web Server 的性能下降,響應時間變長。
- 資源耗盡:如果并發請求過多,不斷創建新的連接可能會耗盡數據庫服務器和 Web Server 的資源,導致系統崩潰。
- 管理困難:大量的連接會增加管理的難度,例如難以控制連接的數量和狀態,容易出現連接泄漏等問題。
SqlConnectPool 成員變量
int max_connect_;
std::queue<MYSQL*> connect_queue_;
std::mutex mtx_;
sem_t sem_id_;
connect_queue_
:使用隊列存儲數據庫連接。sem_id_
:信號量,用于控制連接的獲取和釋放,確保連接的數量不超過最大連接數。
實現 Init() 函數
- 遍歷指定的連接數量,使用
mysql_init
初始化 MySQL 連接對象,使用mysql_real_connect
建立與數據庫的連接。 - 如果連接成功,將連接對象存入連接隊列,并增加最大連接數。
- 最后使用
sem_init
初始化信號量,信號量的初始值為最大連接數。
void SqlConnectPool::Init(const char* host, uint16_t port, const char* user, const char* pwd,const char* db_name, int connect_size) {assert(connect_size > 0);for (int i = 0; i < connect_size; ++i) {MYSQL* connect = nullptr;connect = mysql_init(nullptr);if (!connect) {LOG_ERROR("MySql init fail");assert(connect);}if (!mysql_real_connect(connect, host, user, pwd, db_name, port, nullptr, 0)) {LOG_ERROR("MySql failed to connect to database: Error: %s", mysql_error(connect));} else {connect_queue_.emplace(connect);max_connect_++;}}assert(max_connect_ > 0);// 線程級信號量sem_init(&sem_id_, 0, max_connect_);
}
實現 ClosePool() 函數
- 使用
std::lock_guard
加鎖,確保多線程環境下對連接隊列的安全訪問。 - 遍歷連接隊列,使用
mysql_close
關閉所有的數據庫連接,并調用mysql_library_end
結束 MySQL 庫的使用。
void SqlConnectPool::ClosePool() {std::lock_guard<std::mutex> locker(mtx_);while (!connect_queue_.empty()) {MYSQL* connect = connect_queue_.front();connect_queue_.pop();mysql_close(connect);}mysql_library_end();
}
SqlConnectRAII 類
在構造函數中從連接池獲取一個數據庫連接,在析構函數中自動將連接放回連接池,確保連接的正確釋放,避免內存泄漏。
// 構造時初始化,析構時釋放
class SqlConnectRAII {
public:SqlConnectRAII(MYSQL** sql, SqlConnectPool* pool) {assert(pool);*sql = pool->GetConnect();sql_ = *sql;pool_ = pool;}~SqlConnectRAII() {if (sql_)pool_->FreeConnect(sql_);}private:MYSQL* sql_;SqlConnectPool* pool_;
};
SqlConnectPool 代碼
sql_connect_pool.h
#ifndef SQL_CONNECT_POOL_H
#define SQL_CONNECT_POOL_H#include <string>
#include <queue>
#include <mutex>
#include <thread>
#include <cassert>
#include <semaphore.h>
#include <mysql/mysql.h>
#include "../log/log.h"class SqlConnectPool {
public:static SqlConnectPool* GetInstance();void Init(const char* host, uint16_t port, const char* user, const char* pwd,const char* db_name, int connect_size = 10);void ClosePool();MYSQL* GetConnect();void FreeConnect(MYSQL* connect);int GetFreeConnectCount();private:SqlConnectPool() = default;~SqlConnectPool() { ClosePool(); }int max_connect_;std::queue<MYSQL*> connect_queue_;std::mutex mtx_;sem_t sem_id_;
};// 構造時初始化,析構時釋放
class SqlConnectRAII {
public:SqlConnectRAII(MYSQL** sql, SqlConnectPool* pool) {assert(pool);*sql = pool->GetConnect();sql_ = *sql;pool_ = pool;}~SqlConnectRAII() {if (sql_)pool_->FreeConnect(sql_);}private:MYSQL* sql_;SqlConnectPool* pool_;
};#endif // SQL_CONNECT_POOL_H
sql_connect_pool.cc
#include "sql_connect_pool.h"SqlConnectPool* SqlConnectPool::GetInstance() {static SqlConnectPool pool;return &pool;
}void SqlConnectPool::Init(const char* host, uint16_t port, const char* user, const char* pwd,const char* db_name, int connect_size) {assert(connect_size > 0);for (int i = 0; i < connect_size; ++i) {MYSQL* connect = nullptr;connect = mysql_init(nullptr);if (!connect) {LOG_ERROR("MySql init fail");assert(connect);}if (!mysql_real_connect(connect, host, user, pwd, db_name, port, nullptr, 0)) {LOG_ERROR("MySql failed to connect to database: Error: %s", mysql_error(connect));} else {connect_queue_.emplace(connect);max_connect_++;}}assert(max_connect_ > 0);// 線程級信號量sem_init(&sem_id_, 0, max_connect_);
}void SqlConnectPool::ClosePool() {std::lock_guard<std::mutex> locker(mtx_);while (!connect_queue_.empty()) {MYSQL* connect = connect_queue_.front();connect_queue_.pop();mysql_close(connect);}mysql_library_end();
}MYSQL* SqlConnectPool::GetConnect() {MYSQL* connect = nullptr;if (connect_queue_.empty()) {LOG_WARN("SqlConnectPool busy!");return nullptr;} sem_wait(&sem_id_); // -1std::lock_guard<std::mutex> locker(mtx_);connect = connect_queue_.front();connect_queue_.pop();return connect;
}// 存入連接池
void SqlConnectPool::FreeConnect(MYSQL* connect) {assert(connect);std::lock_guard<std::mutex> locker(mtx_);connect_queue_.push(connect);sem_post(&sem_id_); // +1
}int SqlConnectPool::GetFreeConnectCount() {std::lock_guard<std::mutex> locker(mtx_);return connect_queue_.size();
}
SqlConnectPool 測試
測試SqlConnectPool中MYSQL的初始化,連接,以及查詢語句,以及SqlConnRAII 類
#include "../code/pool/sql_connect_pool.h"
#include <iostream>// 測試 SqlConnectPool 類的功能
void TestSqlConnectPool() {// 初始化日志系統Log* logger = Log::GetInstance();logger->Init(0, "./logs/", ".log", 1024);// 獲取 SqlConnectPool 的單例實例SqlConnectPool* pool = SqlConnectPool::GetInstance();// 初始化連接池const char* host = "localhost";uint16_t port = 3306;const char* user = "Tian";const char* pwd = "123456";const char* db_name = "web_server";int connect_size = 10;pool->Init(host, port, user, pwd, db_name, connect_size);// 測試獲取連接MYSQL* conn = pool->GetConnect();assert(conn != nullptr); // 確保獲取到的連接不為空// 執行簡單的 SQL 查詢if (mysql_query(conn, "select 1")) {std::cerr << "query failed: " << mysql_error(conn) << std::endl;} else {MYSQL_RES* result = mysql_store_result(conn);if (result) {MYSQL_ROW row = mysql_fetch_row(result);if (row) {std::cout << "Query result: " << row[0] << std::endl;}mysql_free_result(result);}}// 測試釋放連接pool->FreeConnect(conn);// 測試獲取空閑連接數量int freeCount = pool->GetFreeConnectCount();std::cout << "Free connection count: " << freeCount << std::endl;// 測試 SqlConnRAII 類{MYSQL* sql = nullptr;SqlConnectRAII raii(&sql, pool);assert(sql != nullptr); // 確保通過 RAII 獲取到的連接不為空} // 離開作用域,自動釋放連接// 關閉連接池pool->ClosePool();
}int main() {TestSqlConnectPool();return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(buffer_unit_test)# 設置 C++ 標準和編譯器選項
# set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")# 定義公共源文件和特定文件
set(COMMON_SOURCES ../code/buffer/buffer.cc ../code/log/log.cc)
set(POOL_SOURCE ../code/pool/sql_connect_pool.cc)# 查找 MySQL 庫
find_package(PkgConfig REQUIRED)
pkg_check_modules(MYSQL REQUIRED mysqlclient)
# 包含 MySQL 頭文件目錄
include_directories(${MYSQL_INCLUDE_DIR})# 添加可執行文件
add_executable(sql_connect_pool_test sql_connect_pool_test.cc ${COMMON_SOURCES} ${POOL_SOURCE})# 鏈接庫
target_link_libraries(sql_connect_pool_test ${MYSQL_LIBRARIES})