C++版mysql数据库连接池
数据库连接池
⽂章⽬录
返回的是⼀个含connection的shared_ptr对象
关键技术点
MySQL数据库编程、单例模式、queue队列容器、C++11多线程编程、线程互斥、线程同步通信(mutex
、条件变量)和 unique_lock、基于CAS的原⼦整形、智能指针shared_ptr、lambda表达式、⽣产者-消费者线程模型
项⽬背景
为了提⾼MySQL数据库(基于C/S设计)的访问瓶颈,除了在服务器端增加缓存服务器缓存常⽤的数据 之外(例如redis),还可以增加连接池,来提⾼MySQL Server的访问效率,在⾼并发情况下,⼤量的 TCP三次握⼿、MySQL Server连接认证、MySQL Server关闭连接回收资源和TCP四次挥⼿所耗费的 性能时间也是很明显的,增加连接池就是为了减少这⼀部分的性能损耗。
本项⽬是为了在C/C++项⽬中,提供MySQL Server的访问效率,所实现基于C++代码的数据库连接池模块。
连接池功能点介绍
连接池⼀般包含了数据库连接所⽤的ip地址、port端⼝号、⽤户名和密码以及其它的性能参数,例如初 始连接量,最⼤连接量,最⼤空闲时间、连接超时时间等,该项⽬是基于C++语⾔实现的连接池,主要 也是实现以上⼏个所有连接池都⽀持的通⽤基础功能。
初始连接量(initSize):表⽰连接池事先会和MySQL Server创建initSize个数的connection连接,当
应⽤发起MySQL访问时,不⽤再创建和MySQL Server新的连接,直接从连接池中获取⼀个可⽤的连接 就可以,使⽤完成后,并不去释放connection,⽽是把当前connection再归还到连接池当中。
最⼤连接量(maxSize):当并发访问MySQL Server的请求增多时,初始连接量已经不够使⽤了,此 时会根据新的请求数量去创建更多的连接给应⽤去使⽤,但是新创建的连接数量上限是maxSize,不能 ⽆限制的创建连接,因为每⼀个连接都会占⽤⼀个socket资源,⼀般连接池和服务器程序是部署在⼀台 主机上的,如果连接池占⽤过多的socket资源,那么服务器就不能接收太多的客户端请求了。当这些连接使⽤完成后,再次归还到连接池当中来维护。
最⼤空闲时间(maxIdleTime):当访问MySQL的并发请求多了以后,连接池⾥⾯的连接数量会动态 增加,上限是maxSize个,当这些连接⽤完再次归还到连接池当中。如果在指定的maxIdleTime⾥⾯, 这些新增加的连接都没有被再次使⽤过,那么新增加的这些连接资源就要被回收掉,只需要保持初始连 接量initSize个连接就可以了。
连接超时时间(connectionTimeout):当MySQL的并发请求量过⼤,连接池中的连接数量已经到达 maxSize了,⽽此时没有空闲的连接可供使⽤,那么此时应⽤从连接池获取连接⽆法成功,它通过阻塞 的⽅式获取连接的时间如果超过connectionTimeout时间,那么获取连接失败,⽆法访问数据库。 该项⽬主要实现上述的连接池四⼤功能,其余连接池更多的扩展功能,可以⾃⾏实现。
MySQL Server参数介绍及涉及API
mysql> show variables like ‘max_connections’; 该命令可以查看MySQL Server所⽀持的最⼤连接个数,超过max_connections数量的连接,MySQL Server会直接拒绝,所以在使⽤连接池增加连接数量的时候,MySQL Server的max_connections参数 也要适当的进⾏调整,以适配连接池的连接上限。
功能实现设计
ConnectionPool.cpp和ConnectionPool.h:连接池代码实现
Connection.cpp和Connection.h:数据库操作代码、增删改查代码实现
连接池主要包含了以下功能点:
1.连接池只需要⼀个实例,所以ConnectionPool以单例模式进⾏设计
2.从ConnectionPool中可以获取和MySQL的连接Connection
3.空闲连接Connection全部维护在⼀个线程安全的Connection队列中,使⽤线程互斥锁保证队列的线程安全
4.如果Connection队列为空,还需要再获取连接,此时需要动态创建连接,上限数量是maxSize
5.队列中空闲连接时间超过maxIdleTime的就要被释放掉,只保留初始的initSize个连接就可以了,这个功能点肯定需要放在独⽴的线程中去做
6.如果Connection队列为空,⽽此时连接的数量已达上限maxSize,那么等待connectionTimeout时间 如果还获取不到空闲的连接,那么获取连接失败,此处从Connection队列获取空闲连接,可以使⽤带 超时时间的mutex互斥锁来实现连接超时时间
7.⽤户获取的连接⽤shared_ptr智能指针来管理,⽤lambda表达式定制连接释放的功能(不真正释放连接,⽽是把连接归还到连接池中)
8.连接的⽣产和连接的消费采⽤⽣产者-消费者线程模型来设计,使⽤了线程间的同步通信机制条件变量和互斥锁
连接池代码及详细介绍
主要功能为6个函数
获取实例、加载配置项、构造函数、
⽣产者线程(连接⽤完了就补充⼀点)、消费者(接⼝)、定时线程(回收资源)
shared_ptr 析构 ⾃定义删除器,回线程
class ConnectionPool
{
public:
// 获取连接池对象实例
static ConnectionPool* getConnectionPool();
// 给外部提供接⼝,从连接池中获取⼀个可⽤的空闲连接
shared_ptr<Connection> getConnection(); // 接⼝采⽤RAII,不需要⽤户管理资源private:
// 单例#1 构造函数私有化
ConnectionPool();
// 从配置⽂件中加载配置项
bool loadConfigFile();
// 运⾏在独⽴的线程中,专门负责⽣产新连接
void produceConnectionTask();
/
/ 扫描超过maxIdleTime时间的空闲连接,进⾏对于的连接回收
void scannerConnectionTask();
string _ip; // mysql的ip地址
unsigned short _port; // mysql的端⼝号 3306
string _username; // mysql登录⽤户名
string _password; // mysql登录密码
string _dbname; // 连接的数据库名称
int _initSize; // 连接池的初始连接量
int _maxSize; // 连接池的最⼤连接量
int _maxIdleTime; // 连接池最⼤空闲时间
int _connectionTimeout; // 连接池获取连接的超时时间
queue<Connection*> _connectionQue; // 存储mysql连接的队列
mutex _queueMutex; // 维护连接队列的线程安全互斥锁
atomic_int _connectionCnt; // 记录连接所创建的connection连接的总数量
condition_variable cv; // 设置条件变量,⽤于连接⽣产线程和连接消费线程的通信};
连接池构造函数
ConnectionPool::ConnectionPool()
{
// 加载配置项了
if (!loadConfigFile())
{
return;
}
// 创建初始数量的连接
for (int i = 0; i < _initSize; ++i)
{
Connection* p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
p->refreshAliveTime(); // 刷新⼀下开始空闲的起始时间
_connectionQue.push(p);
_connectionCnt++;
}
// 启动⼀个新的线程,作为连接的⽣产者 linux thread => pthread_create
mysql下载add producethread produce(std::bind(&ConnectionPool::produceConnectionTask, this));
produce.detach();
// 启动⼀个新的定时线程,扫描超过maxIdleTime时间的空闲连接,进⾏对于的连接回收 thread scanner(std::bind(&ConnectionPool::scannerConnectionTask, this));
scanner.detach();
}
获取单例
// 线程安全的懒汉单例函数接⼝调⽤的时候再构造
ConnectionPool* ConnectionPool::getConnectionPool()
{
static ConnectionPool pool; // lock和unlock
return &pool;
}
⽣产者线程
// 运⾏在独⽴的线程中,专门负责⽣产新连接
void ConnectionPool::produceConnectionTask()
{
for (;;)
{
unique_lock<mutex> lock(_queueMutex);
while (!_pty())
{
cv.wait(lock); // 队列不空,此处⽣产线程进⼊等待状态,释放锁
}
// 连接数量没有到达上限,继续创建新的连接
if (_connectionCnt < _maxSize)
{
Connection* p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
p->refreshAliveTime(); // 刷新⼀下开始空闲的起始时间
_connectionQue.push(p);
_connectionCnt++;
}
// 通知消费者线程,可以消费连接了
}
}
// 给外部提供接⼝,从连接池中获取⼀个可⽤的空闲连接
shared_ptr<Connection> ConnectionPool::getConnection()
{
unique_lock<mutex> lock(_queueMutex);
while (_pty())
{
// sleep
if (cv_status::timeout == cv.wait_for(lock, chrono::milliseconds(_connectionTimeout)))
{
if (_pty())
{
LOG("获取空闲连接超时了...获取连接失败!");
return nullptr;
}
}
}
/*
shared_ptr智能指针析构时,会把connection资源直接delete掉,相当于
调⽤connection的析构函数,connection就被close掉了。
这⾥需要⾃定义shared_ptr的释放资源的⽅式,把connection直接归还到queue当中
*/
shared_ptr<Connection> sp(_connectionQue.front(),
[&](Connection* pcon) {
// 这⾥是在服务器应⽤线程中调⽤的,所以⼀定要考虑队列的线程安全操作
unique_lock<mutex> lock(_queueMutex);
pcon->refreshAliveTime(); // 刷新⼀下开始空闲的起始时间
_connectionQue.push(pcon);
});
_connectionQue.pop();
return sp;
}
定时线程(回收连接)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论