c++ 数据库连接池(二)
MysqlConnectionPool
类总结
类的作用
MysqlConnectionPool
是一个数据库连接池管理类,通过单例模式实现。- 它通过管理一个连接队列来优化数据库连接的使用,减少频繁建立和销毁连接的开销。
- 使用生产者-消费者模型:
- 生产者:负责创建新的数据库连接(
produceConnect
)。 - 消费者:通过
getConnection
方法从连接池中获取可用连接。 - 定期清理多余的或超时的连接(
dropConnection
)。
- 生产者:负责创建新的数据库连接(
单例模式
- 通过单例模式保证全局只有一个连接池实例,防止多个连接池实例管理的连接资源冲突。
类的主要功能
1. 单例模式
- 目的:
- 确保连接池全局只有一个实例,统一管理所有数据库连接。
- 实现方法:
- 构造函数设置为
private
,避免外部直接实例化。 - 提供一个静态方法
instance()
返回唯一实例。 - 禁用拷贝构造和赋值操作(
delete
拷贝构造函数和赋值操作符)。
- 构造函数设置为
2. 配置文件解析
- 方法:
bool parseJsonFile()
- 从 JSON 文件中读取数据库连接的配置信息,包括用户名、密码、数据库名称、主机地址、端口号、最小连接数、最大连接数、超时时间等。
- 这些配置用于初始化和管理连接池。
3. 生产者线程
- 方法:
void produceConnect()
- 当连接池中的连接数小于最小连接数时,负责创建新的数据库连接。
- 将创建的连接添加到队列中,供消费者使用。
4. 消费者线程
- 方法:
std::shared_ptr<MysqlConn> getConnection()
- 从连接池中获取一个可用的数据库连接。
- 使用
std::shared_ptr
管理连接的生命周期,并通过自定义删除器将连接归还到池中。
5. 删除多余或超时连接
- 方法:
void dropConnection()
- 定期检查连接池中的空闲连接。
- 删除空闲时间超过最大空闲时间的连接,以节约资源。
6. 创建连接
- 方法:
void addConnection()
- 用于创建一个新的数据库连接并加入到队列中。
私有成员变量
变量名 | 类型 | 描述 |
---|---|---|
m_user |
std::string |
数据库用户名。 |
m_passwd |
std::string |
数据库密码。 |
m_dbName |
std::string |
数据库名称。 |
m_host |
std::string |
数据库主机地址(如 127.0.0.1 或 localhost )。 |
m_port |
unsigned short |
数据库端口号(如 MySQL 默认的 3306 )。 |
m_minSize |
int |
连接池的最小连接数,确保池中始终有足够的空闲连接。 |
m_maxSize |
int |
连接池的最大连接数,防止连接数无限增长导致资源耗尽。 |
m_timeout |
int |
消费者获取连接的最大等待时间(单位:毫秒)。 |
m_maxIdleTime |
int |
连接的最大空闲时间(单位:秒),超过此时间未使用的连接将被删除。 |
m_SQLConnectQ |
std::queue<MysqlConn*> |
用于存储所有空闲的数据库连接的队列。 |
m_mutex |
std::mutex |
用于保护 m_SQLConnectQ 的互斥锁,保证线程安全。 |
m_cond |
std::condition_variable |
用于连接池的线程间同步,消费者等待连接,生产者通知。 |
类的主要方法
方法名 | 类型 | 描述 |
---|---|---|
MysqlConnectionPool() |
explicit |
私有构造函数,用于初始化连接池(如读取 JSON 配置)。 |
~MysqlConnectionPool() |
~MysqlConnectionPool |
析构函数,释放所有空闲连接,清空队列。 |
instance() |
static |
获取连接池的唯一实例。 |
getConnection() |
std::shared_ptr<MysqlConn> |
消费者调用,从连接池中获取一个可用的数据库连接。 |
produceConnect() |
void |
生产者调用,生成新的连接并加入队列。 |
addConnection() |
void |
创建新的数据库连接,初始化后加入连接队列。 |
dropConnection() |
void |
删除多余的或空闲超时的连接,释放资源。 |
parseJsonFile() |
bool |
从 JSON 文件读取数据库配置参数。 |
线程安全的设计
-
使用
std::mutex
:- 保护连接队列
m_SQLConnectQ
的操作,避免多个线程同时修改队列引发的竞争问题。
- 保护连接队列
-
使用
std::condition_variable
:- 通过生产者-消费者模型协调线程,确保消费者在队列为空时等待,生产者在添加新连接后唤醒消费者。
-
通过
std::shared_ptr
管理连接:- 使用自定义删除器在连接被销毁时自动归还到连接池,避免手动管理的麻烦。
典型的工作流程
-
启动连接池:
- 通过
MysqlConnectionPool::instance()
获取单例实例。 - 从 JSON 配置文件中读取数据库参数,并初始化连接池。
- 通过
-
生产连接:
- 在连接池空闲连接数不足时,生产者线程调用
produceConnect()
,创建新的数据库连接并加入队列。
- 在连接池空闲连接数不足时,生产者线程调用
-
消费连接:
- 需要数据库连接的线程调用
getConnection()
,从队列中取出一个连接。 - 使用
std::shared_ptr
管理连接,确保连接使用完毕后自动归还到池中。
- 需要数据库连接的线程调用
-
清理连接:
- 定期调用
dropConnection()
,删除空闲时间过长的连接,保持资源高效利用。
- 定期调用
优点
-
资源复用:
- 通过连接池重复利用连接,避免频繁创建和销毁数据库连接带来的开销。
-
线程安全:
- 使用互斥锁和条件变量,保证多线程操作的安全性。
-
可扩展性:
- 支持从配置文件动态调整连接池的参数(如最小/最大连接数、超时时间等)。
-
自动化管理:
- 通过生产者-消费者模型,动态管理连接池的连接数,自动回收超时连接。
总结
MysqlConnectionPool
是一个基于单例模式的数据库连接池类,使用生产者-消费者模型动态管理连接。它通过线程安全的队列管理空闲连接,支持高效的数据库操作,同时通过 JSON 配置文件实现灵活的参数调整。这个类适用于高并发场景,能够有效提高系统性能并简化数据库连接管理。
代码
MysqlConnectionPool
类头文件
为了配置服务器一起关闭和开启数据库连接池,我们增加了start和stop函数,连接池的初始化放在了start函数,新增了m_stop标志。
当服务器关闭时,也需要关闭连接池。详情见代码。
/**
******************************************************************************
* @file : MysqlConnectionPool.h
* @author : sally
* @brief : None
* @attention : None
* @date : 25-1-28
******************************************************************************
*/
#ifndef MYSQLCONNECTIONPOOL_H
#define MYSQLCONNECTIONPOOL_H
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include "MysqlConn.h"
//单例模式
//这个类的作用就是通过一个队列来管理多个连接
//采用生成者-消费者模型,一个线程来创建连接,同时一个线程来删除多余的且超时的连接。
//消费者通过getConnection来获取连接。
//同时数据库的配置信息使用json来配置并从文件中读取。
class MysqlConnectionPool
{
public:
MysqlConnectionPool(const MysqlConnectionPool&) = delete;
~MysqlConnectionPool();
MysqlConnectionPool& operator =(const MysqlConnectionPool&) = delete;
static MysqlConnectionPool& instance();
std::shared_ptr<MysqlConn> getConnection();
private:
void produceConnect();
void addConnection();
void dropConnection();
bool parseJsonFile();
private:
explicit MysqlConnectionPool();
std::string m_user;
std::string m_passwd;
std::string m_dbName;
std::string m_host;
unsigned short m_port;
int m_minSize;
int m_maxSize;
int m_timeout;
int m_maxIdleTime;
std::queue<MysqlConn*> m_SQLConnectQ;
std::mutex m_mutex;
std::condition_variable m_cond;
};
#endif //MYSQLCONNECTIONPOOL_H
MysqlConnectionPool
类源文件
/**
******************************************************************************
* @file : MysqlConnectionPool.cpp
* @author : sally
* @brief : None
* @attention : None
* @date : 25-1-28
******************************************************************************
*/
#include "MysqlConnectionPool.h"
#include <config.h>
#include <jsoncpp/json/json.h>
#include <fstream>
#include <thread>
MysqlConnectionPool::~MysqlConnectionPool()
{
while (!m_SQLConnectQ.empty())
{
MysqlConn* conn = m_SQLConnectQ.front();
m_SQLConnectQ.pop();
delete conn;
}
}
MysqlConnectionPool& MysqlConnectionPool::instance()
{
static MysqlConnectionPool mysqlConnectPool;
return mysqlConnectPool;
}
/**
* 这是消费者
* @return 返回数据库连接指针
*/
std::shared_ptr<MysqlConn> MysqlConnectionPool::getConnection()
{
//加锁
std::unique_lock<std::mutex> lock(m_mutex);
while (m_SQLConnectQ.empty())
{
//如果是空,则需要等待
if (std::cv_status::timeout == m_cond.wait_for(lock, std::chrono::milliseconds(m_timeout)))
{
//如果是超时的话,就继续判断是否为空
if (m_SQLConnectQ.empty())
continue;
//如果不是空的,则会跳出while循环的
}
}
std::shared_ptr<MysqlConn> sqlConnectPtr(m_SQLConnectQ.front(), [this](MysqlConn* conn)
{
std::lock_guard<std::mutex> lock(m_mutex);
conn->refreshAliveTime();
m_SQLConnectQ.push(conn);
});
//需要弹出
m_SQLConnectQ.pop();
//唤醒其它在等待的线程,因为当有一个线程进入到wait_for时,会释放掉锁,然后其它的线程也可能进入wait_for了。
m_cond.notify_all();
return sqlConnectPtr;
}
/**
* 使用json来解析json文件的步骤:
* 1、读取文件
* 2、创建json解析器
* 3、创建存储解析后的json数据结构(对象)
* 4、解析
*/
void MysqlConnectionPool::start()
{
m_stop = false;
if (!parseJsonFile())
{
OPERATION_FAIL("CA_EVP_PriKey is nullptr", "CertificateIssuance", __FILE__, __LINE__, "");
throw std::runtime_error("Failed to read database configuration information");
}
while (m_SQLConnectQ.size() < m_minSize)
{
addConnection();
}
std::thread producer(&MysqlConnectionPool::produceConnection, this);
std::thread droper(&MysqlConnectionPool::dropConnection, this);
producer.detach();
droper.detach();
}
void MysqlConnectionPool::stop()
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_stop = true;
}
m_cond.notify_all();
}
/**
* 这个函数的作用就是添加数据库连接,连接池里面存放的是空闲的数据库连接,当某个连接被取出时,连接队列的连接数量就减1了。
* 最小的空闲连接池是100,超过100就不添加数据库连接了。
*/
void MysqlConnectionPool::produceConnection()
{
//
while (!m_stop)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_cond.wait(lock, [this] { return m_stop || m_SQLConnectQ.size() < m_minSize; });
if (m_stop)
{
//这里说明是要退出时唤醒的,就退出就行
break;
}
//唤醒之后就会进入到这里,到这里唤醒有两种情况一种是退出时的唤醒,一种是是取出了连接,或者连接被删除时这两种情况导致的空闲线程不足了。
//小于m_minSize,则需要添加连接池
if (m_SQLConnectQ.size() < m_minSize)
{
addConnection();
//添加完毕,需要唤醒等待数据库连接的线程
m_cond.notify_all();
}
}
}
void MysqlConnectionPool::addConnection()
{
MysqlConn* conn = new MysqlConn();
//连接数据库
if (conn->connect(m_user, m_passwd, m_dbName, m_host, m_port))
{
conn->refreshAliveTime();
m_SQLConnectQ.push(conn);
std::cout << "102 m_SQLConnectQ.size : " << m_SQLConnectQ.size() << std::endl;
}
else
{
delete conn;
}
}
/**
* 这个函数的作用就是用来删除空闲时间大于最大空闲时间的连接。
* 一个连接从最后一次活动时间到现在这段时间就是空闲时间。
*/
void MysqlConnectionPool::dropConnection()
{
while (!m_stop)
{
//如果空闲的连接数大于了最小的空闲连接数,说明空闲连接多起来了,
std::unique_lock<std::mutex> lock(m_mutex);
m_cond.wait_for(lock, std::chrono::milliseconds(500),
[this]() { return m_stop || m_SQLConnectQ.size() > m_minSize; });
if (m_stop)
break;
//只有当连接大于最小的连接时,才会开始删除多余的连接,最小的连接用来保证连接数的呀
//清理空闲的线程
//获取一个连接
//这里再次检查,避免 `wait_for()` 误触发导致错误删除连接
if (m_SQLConnectQ.size() > m_minSize)
{
MysqlConn* conn = m_SQLConnectQ.front();
if (conn->getAliveTime() >= m_maxIdleTime)
{
m_SQLConnectQ.pop();
delete conn;
std::cout << "drop mysql connect, m_SQLConnectQ size is: " << m_SQLConnectQ.size() << std::endl;
//删除后通知 `produceConnection()` 线程,让它检查是否需要创建新连接
m_cond.notify_all();
}
}
}
}
bool MysqlConnectionPool::parseJsonFile()
{
//打开json文件
std::ifstream inputFile("../../src/VideoServer/utils/dbconfig.json", std::ios_base::in);
Json::Reader rd; //一个 JSON 解析器对象
Json::Value root; //用于存储解析后的 JSON 数据结构,类似于一个树形结构的根节点。类似于proto里面的对象
//这里就是解析文件到root数据结构中
rd.parse(inputFile, root);
inputFile.close();
//判断root是不是一个json对象
if (root.isObject())
{
m_host = root["host"].asString();
m_port = root["port"].asInt();
m_user = root["userName"].asString();
m_dbName = root["dbName"].asString();
m_passwd = root["password"].asString();
m_minSize = root["minSize"].asInt();
m_maxSize = root["maxSize"].asInt();
m_maxIdleTime = root["maxIdleTime"].asInt();
m_timeout = root["timeout"].asInt();
return true;
}
return false;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具