一、MySQL连接池
1.1、什么是连接池
复用一定数量的池式结构。
1.2、解决了什么问题?
从这张图可以看到,一个连接的开销非常大,会经历连接建立、认证、权限校验等。如果频繁地创建和关闭数据库连接,将会极大地影响性能。
所以需要连接池复用连接和内存资源,提升并发处理SQL的能力。
1.3、如何实现?
同步连接池:
- 使用MySQL给的驱动或相关API实现。(libmysqlclient—纯C语言实现,libmysqlcppconn—C++封装,加入了异常处理机制)
- 初始化内存资源,在服务器启动时使用
异步连接池:
- 需要实现MySQL的协议。
- 处理业务
二、同步连接池实现方案
2.1、使用官方驱动
下载官方给的驱动或者库
官方链接
2.2、连接池管理
使用一个队列管理连接
std::queue<std::shared_ptr<sql::Connection>> m_freeConnPool; // 使用智能指针进行自动化的内存管理
2.3、线程安全
模拟并发场景,智能指针中的引用计数是线程安全的,但是智能指针所指对象的线程安全问题需要开发者自行保证,所以需要通过互斥锁和条件变量类似机制来保证线程安全。
2.4、资源管理
当一个连接不再使用时,需要归还到池中,而不是直接关闭,这样可以复用连接。
基于以上几点,那么可以得到这样的类
class ConnectPool
{
public:
static ConnectPool* getInstance(){
static ConnectPool m_instance;
return &m_instance;
}
void initialize(const mysqlinfo &info);
std::shared_ptr<sql::Connection> getConnection();
void returnConnection(std::shared_ptr<sql::Connection> conn);
void printInfo()const;
private:
ConnectPool()
{
m_driver = nullptr;
m_isInit = false;
}
~ConnectPool(){
while(!m_freeConnPool.empty()){
auto conn = m_freeConnPool.front();
m_freeConnPool.pop();
if(!conn->isClosed()){
conn->close();
}
}
}
ConnectPool(const ConnectPool&) = delete;
ConnectPool& operator=(const ConnectPool&) = delete;
ConnectPool(ConnectPool&&) = delete;
std::shared_ptr<sql::Connection> createConnection();
sql::Driver* m_driver;
std::queue<std::shared_ptr<sql::Connection>> m_freeConnPool;
mutable std::mutex m_mtx;
std::condition_variable m_condition;
mysqlinfo m_sqlInfo;
bool m_isInit;
};
根据单例模式的思想,只有一个连接池实例,并提供初始化、获取连接和归还连接的接口。
那是怎么归还连接呢?
/**
* @brief 创建数据库连接
*
* 使用提供的数据库驱动和连接信息创建数据库连接,并将其加入空闲连接池。
*
* @return 成功时返回连接对象的共享指针,失败时返回nullptr。
*/
std::shared_ptr<sql::Connection> ConnectPool::createConnection()
{
try{
// 使用lambda表达式自定义删除器,确保连接在不再使用时被归还到池中
auto conn = std::shared_ptr<sql::Connection>(
m_driver->connect(m_sqlInfo.host, m_sqlInfo.user, m_sqlInfo.passwd),
[this](sql::Connection* conn){
returnConnection(std::shared_ptr<sql::Connection>(conn));
}
);
conn->setSchema(m_sqlInfo.dbname);
m_freeConnPool.push(conn);
return conn;
}catch(sql::SQLException &e){
std::cerr<<"Failed to create connection: "<<e.what()<<std::endl;
return nullptr;
}
}
/**
* @brief 归还数据库连接到连接池
*
* 将使用完毕的数据库连接对象安全地返回到连接池中。函数会检查连接有效性,
* 若连接已关闭则会创建新连接替代,保证连接池的健康状态。
*
* @param conn 指向sql::Connection对象的共享指针,表示要归还的数据库连接
*
* @note 使用互斥锁保证线程安全,归还有效连接时会唤醒等待连接的线程
*/
void ConnectPool::returnConnection(std::shared_ptr<sql::Connection> conn)
{
std::lock_guard<std::mutex> lock(m_mtx);
// 检查连接是否有效
if(conn->isValid() && !conn->isClosed()){
m_freeConnPool.push(conn);
m_condition.notify_one();
}else{
// 如果连接已关闭,创建新的连接代替
std::cout<<"Connection is closed, creating a new one...\n";
createConnection();
}
}
那从连接池中获取一个连接对象,连接池为空怎么办?
/**
* @brief 获取数据库连接
*
* 获取一个指向 sql::Connection 对象的 shared_ptr 智能指针。
*
* @return std::shared_ptr<sql::Connection> 指向 sql::Connection 对象的智能指针
*/
std::shared_ptr<sql::Connection> ConnectPool::getConnection()
{
std::unique_lock<std::mutex> lock(m_mtx);
// 等待直到有可用连接
if(m_freeConnPool.empty()){
m_condition.wait(lock, [this](){ return !m_freeConnPool.empty(); });
}
auto conn = m_freeConnPool.front();
m_freeConnPool.pop();
// 检查链接是否有效
if(!conn->isValid() || conn->isClosed()){
try{
conn->reconnect();
std::cout<<"Reconnecting..."<<std::endl;
}catch(sql::SQLException &e){
std::cerr<<"Failed to reconnect: "<<e.what()<<std::endl;
conn = createConnection();
}
}
return conn;
}
2.5、测试场景
创建10个线程并发访问数据库,连接池的容量设置为5,测试连接池是否正常工作。
int main()
{
auto pool = ConnectPool::getInstance();
mysqlinfo info("tcp://127.0.0.1:3306", "root", "123456", "sql", Port, Mysql_Max_Connect);
pool->initialize(info);
pool->printInfo();
// 创建多个线程,每个线程都从连接池中获取一个连接并执行一些操作
std::vector<std::thread> threads;
for(int i = 0; i < Max_Thread; ++i){
threads.emplace_back(threadWort, i);
}
for(auto &t : threads){
t.join();
}
pool->printInfo();
return 0;
}
可以看见,能够正常地从连接池中获取到连接,并且在归还时能够正确地处理无效的连接,实现连接对象的复用
三、拓展
官方给出的相关API,无明显看到与服务器建立套接字连接的API;实际上是被封装在底层驱动了;官方为了支持多系统平台,所以采用的是select
网络IO模型,这也是select
的优点,但缺点也很明显,在高并发场景下性能瓶颈很明显。