连接池空闲连接回收
OVERVIEW
1.需求分析
多余的连接如果超过指定的时间,还没有再次被使用到,就需要将连接进行回收,从而节省服务器资源,
实现方案:
- 在Connection类中添加_alivetime字段,用于记录连接进入空闲状态后的起始时间,
- 每次入队或者出队时都将Connection连接对象的_alivetime字段进行刷新,
- 启动一个定时线程,定时扫描超过maxIdleTime的空闲连接,进行多余连接的回收操作
2.Connection添加字段
#ifndef _CONNECTION_H
#define _CONNECTION_H
#include <ctime>
#include <string.h>
#include <iostream>
#include <mysql/mysql.h>
using namespace std;
#define LOG(str) \
cout << __FILE__ << ":" << __LINE__ << " " << \
__TIMESTAMP__ << ":" << str << endl;
// 封装Connection对象 实现数据库增删改查
class Connection {
public:
// 初始化数据库连接
Connection();
// 释放数据库连接资源
~Connection();
// 连接数据库
bool connect(string ip, unsigned short port, string user, string password, string dbname);
// 更新数据库操作 insert delete update
bool update(string sql);
// 刷新连接的起始空闲时间点
void refreshAliveTime() { _alivetime = clock(); }
// 返回连接存活的时长
clock_t getAliveTime() const { return clock() - _alivetime; }
private:
// 表示和 MYSQL Server的一条连接
MYSQL *_conn;
// 进入空闲状态后的起始时间
clock_t _alivetime;
};
#endif
3.CommonConnectionPool中调用刷新
-
在初始化连接池时,添加刷新Connection连接的alivetime
// 初始化连接池 ConnectionPool::ConnectionPool() { // 1.加载配置项 if (!loadConfigFile()) return; // 2.创建初始数量的连接 for (int i = 0; i < _initSize; ++i) { Connection *p = new Connection(); p->connect(_ip, _port, _username, _password, _dbname); p->refreshAliveTime(); _connectionq.push(p); _connectionCnt++; } // 3.启动一个新的线程作为连接的生产者 thread connProducer(std::bind(&ConnectionPool::produceConnTask, this)); }
-
在消费者生产Connection连接时,刷新连接的alivetime
// 生产者线程的线程函数 void ConnectionPool::produceConnTask() { for (;;) { // 队列上锁 unique_lock<mutex> lock(_queueMutex); // 若队列不空 生产者线程进入等待状态 并且将锁释放 等待为空 while (!_connectionq.empty()) { condv.wait(lock); } // 连接数量没有到达上限 则继续创建新的连接 if (_connectionCnt < _maxSize) { Connection *p = new Connection(); p->connect(_ip, _port, _username, _password, _dbname); p->refreshAliveTime(); _connectionq.push(p); _connectionCnt++; } // 通知消费者线程可以消费连接了 condv.notify_all(); } }
-
在消费者使用完毕,归还数据库连接时,需要刷新连接的alivetime
// 返回连接对象 使用智能指针管理 shared_ptr<Connection> sp(_connectionq.front(), [&](Connection *pcon){ unique_lock<mutex> lock(_queueMutex); pcon->refreshAliveTime(); _connectionq.push(pcon); });
4.CommonConnectionPool中定时扫描
启动一个定时线程,定时扫描超过maxIdleTime的空闲连接,进行多余连接的回收操作
CommonConnectionPool.h
#ifndef _COMMONCONNECTIONPOOL_H
#define _COMMONCONNECTIONPOOL_H
#include <queue>
#include <string>
#include <mutex>
#include <atomic>
#include <thread>
#include <memory>
#include <functional>
#include <condition_variable>
#include "Connection.h"
using namespace std;
// 实现连接池功能模块
class ConnectionPool {
public:
// 获取连接池实例
static ConnectionPool* getConnectionPool();
// 获取数据库连接
shared_ptr<Connection> getConnection();
private:
ConnectionPool();
// 加载外部配置文件
bool loadConfigFile();
// 生产新连接任务函数
void produceConnTask();
// 定时扫描空闲连接 并进行回收操作
void collectConnTask();
private:
// 连接池参数
int _initSize; // 初始连接量
int _maxSize; // 最大连接量
int _maxIdleTime; // 最大空闲时间
int _connectionTimeout; // 连接超时时间
// 数据库信息
string _ip;
unsigned short _port;
string _username;
string _password;
string _dbname;
// 数据库连接存储队列
queue<Connection*> _connectionq; // 存储mysql连接的队列
mutex _queueMutex; // 维护连接队列的线程安全的互斥锁
atomic_int _connectionCnt; // 创建连接的数量 ++操作是线程安全的
condition_variable condv; // 条件变量 用于连接生产线程和连接消费线程间的通信
};
#endif
CommonConnectionPool.cpp
// 初始化连接池
ConnectionPool::ConnectionPool() {
// 1.加载配置项
if (!loadConfigFile()) return;
// 2.创建初始数量的连接
for (int i = 0; i < _initSize; ++i) {
Connection *p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
p->refreshAliveTime();
_connectionq.push(p);
_connectionCnt++;
}
// 3.启动一个新的线程作为连接的生产者
thread connProducer(std::bind(&ConnectionPool::produceConnTask, this));
// 设置为守护线程
connProducer.detach();
// 4.启动一个新的线程 定时扫描超过maxIdleTime时间的空闲连接 对空闲连接进行回收操作
thread connCollector(std::bind(&ConnectionPool::collectConnTask, this));
// 设置为守护线程
connCollector.detach();
}
// 定时扫描空闲连接 并进行回收操作
void ConnectionPool::collectConnTask()
{
for (;;) {
// 通过调用sleep模拟定时效果
this_thread::sleep_for(chrono::seconds(_maxIdleTime));
// 扫描整个连接池队列 释放多余连接
unique_lock<mutex> lock(_queueMutex);
while (_connectionCnt > _initSize) {
Connection *p = _connectionq.front();
if (p->getAliveTime() < (_maxIdleTime * 1000)) break;
_connectionq.pop();
_connectionCnt--;
delete p;//调用Connection的析构函数释放连接
}
}
}