核心思想:
通过在服务端(Server)创建一个连接池队列用于存放和数据库的连接,让访问数据库的客户端可以直接在队列中选取一个可用的连接去访问数据库,用完之后的连接不是被释放掉而是归还给了连接池队列,这样下一个客户端在访问数据库是还可以复用该连接,省去了每一条连接从TCP三次握手建立连接到MYSQL Server的连接认证和MYSQLServer关闭连接回收资源以及四次挥手所消耗的时间。
关键技术点:
MYSQL编程、单例模式、queque队列容器、智能指针、unique_lock、lambda表达式、生产者消费者模型、多线程编程。
常规连接池的四个基础功能:
1.初始连接量:服务端的连接池队列中初始时的和MYSQL数据库创建好的连接个数,当客户端发起MYSQL数据访问时,可以直接从连接池队列中选择一个空闲的连接用于访问数据库,使用结束后将连接重新归还到连接池队列中。
2.最大连接量:如果同一时间内访问数据库的客户端数量很多,导致连接池队列中的可用连接数量为0,此时需要有一个生产者线程来负责生成连接提供给客户端使用,当然也不可以无限制生产,需要有一个连接池容量最大上限。
3.最大空闲时间:生成者生产了多个连接之后,在某一时刻没有客户端访问数据库,此时连接池队列中的连接均处于空闲状态,我们不想同时维护这么多和数据库的连接,因此需要设置一个连接最大空闲时间,当队列中的连接数量大于初始连接量并且连接的空闲时间超过了最大空闲时间时,那么这些连接就要被回收,连接池队列中只保持初始连接量个数的连接。
4.连接超时时间:如果某一时刻连接池队列中的可用连接个数为0,那么该请求会通过阻塞的方式来等待空闲可用连接,但是不能让其无限等待,需要设置一个连接超时时间,如果该请求等待的市场超过了连接超时时间,那么该请求获取链接失败,无法访问数据库。
功能实现流程:
代码模块
主要分为两个模块:
1.Connection.h/Connection.cpp---负责数据库的连接以及数据库的增删改查操作。
2.Connection_pool.h/Connection_pool.cpp--负责实现连接池的各个功能。
先看运行结果数据统计图:
很显然,当数据量越大时,连接池的效果越明显。
代码部分如下所示,思路和细节均在注释中:
/*
程序名:Connection_pool.h
程序功能:实现连接池功能模块
作者:dyy
*/
#pragma once
#include<string>
#include<queue>
#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();//单利#1,构造函数私有化
bool loadConfigFile();//从配置文件中加载配置项
void produceConnTask();//运行在独立的线程中,专门负责生成新连接
void scannerConnectionTask();启动一个新的定时线程,扫描超过maxIdleTime多余的空闲连接,进行多余的连接回收
string _ip; //数据库ip地址
unsigned short _port;//数据库端口号 3306
string _username;//用户名
string _password;//密码
string _dbname;//数据库名称
int _initSize;//初始时连接池的链接个数
int _maxSize;//最大连接数
int _maxIdleTime;//连接池最大空闲时间
int _connectionTimeout;//连接池获取链接的超时时间
queue<Connection*> _connectionQue;//存储mysql连接的队列
mutex _queueMutex;//维护连接队列的线程安全互斥锁
atomic_int _connectionCnt;//记录所创建的链接总数量
condition_variable cv;//设置条件变量,用于连接生成线程和连接消费线程的通信
};
/*
程序名:Connection_pool.cpp
程序功能:实现连接池功能模块
作者:dyy
*/
#include"Connection_pool.h"
#include"Public.h"
//线程安全的懒汉单例接口函数
ConnectionPool* ConnectionPool::getConnectionPool() {
static ConnectionPool Pool;//静态变量由编译器进行lock和unlock,天然生成安全
return &Pool;
}
//从配置文件中加载配置项
bool ConnectionPool::loadConfigFile() {
FILE* pf = fopen("mysql.ini", "r");
if (pf == nullptr) {
LOG("mysql.ini file is not exists!");
return false;
}
while (!feof(pf))//如果文件没有到末尾
{
char line[1024] = { 0 };
fgets(line, 1024, pf);
string str = line;
int idx = str.find('=', 0);
if (idx == -1) {//无效的配置项
continue;
}
int endidx = str.find('\n', idx);
string key = str.substr(0, idx);
string value = str.substr(idx + 1, endidx - idx - 1);
if (key == "ip") {
_ip = value;
}
else if (key == "port") {
_port = atoi(value.c_str());
}
else if (key == "username") {
_username = value;
}
else if (key == "password") {
_password = value;
}
else if (key == "initSize") {
_initSize = atoi(value.c_str());
}
else if (key == "maxSize") {
_maxSize = atoi(value.c_str());
}
else if (key == "maxIdleTime") {
_maxIdleTime = atoi(value.c_str());
}
else if (key == "connectionTimeOut") {
_connectionTimeout = atoi(value.c_str());
}
else if (key == "dbname") {
_dbname = value;
}
}
return true;
}
//连接池的构造函数
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++;
}
//启动一个新的线程作为链接生成者,也就是生产者线程
thread produce(std::bind(&ConnectionPool::produceConnTask,this));
produce.detach();
//启动一个新的定时线程,扫描超过maxIdleTime多余的空闲连接,进行多余的连接回收
thread scanner(std::bind(&ConnectionPool::scannerConnectionTask, this));
scanner.detach();
}
//运行在独立的线程中,专门负责生成新连接
void ConnectionPool::produceConnTask() {
for (;;) {
unique_lock<mutex> lock(_queueMutex);
while (!(_connectionQue.empty())) {
cv.wait(lock); //队列不空,生成线程进入等待状态
}
//连接数量没有到达上限,继续创建新的连接
if (_connectionCnt < _maxSize) {
for (int i = 0; i < _initSize; i++) {
Connection* p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
p->refreshAliveTime();//刷新一下开始空闲的起始时间
_connectionQue.push(p);
_connectionCnt++;
}
}
//通知消费者线程,可以消费连接了
cv.notify_all();
}
}
//给外部提供一个接口,从连接池中获取一个可用的空闲连接
shared_ptr<Connection> ConnectionPool::getConnection() {
unique_lock<mutex>lock(_queueMutex);
while (_connectionQue.empty()) {
//在指定的时间之内,如果有可用的连接,就等待拿连接
if (cv_status::timeout == cv.wait_for(lock, chrono::milliseconds(_connectionTimeout))) {
if (_connectionQue.empty()) {
LOG("获取空闲连接超时...");
return nullptr;
}
}
}
/*
shared_ptr智能指针析构时,会直接把connection资源直接delete掉,相当于调用connection的析构函数
connection就被close掉了
这里需要自定义shared_ptr的释放资源方式,将connection归还到连接队列里面
*/
shared_ptr<Connection> sp(_connectionQue.front(), [&](Connection* pcon) {
unique_lock<mutex> lock(_queueMutex);//保证线程安全
pcon->refreshAliveTime();//刷新一下开始空闲的起始时间
_connectionQue.push(pcon);
});//如果队列不为空,则创建一个智能指针指向队头元素
_connectionQue.pop();
if (_connectionQue.empty()) {
//谁消费了队列中的最后一个连接,谁就要负责通知生成者生成连接
cv.notify_all();
}
return sp;
}
//扫描超过maxIdleTime多余的空闲连接,进行多余的连接回收
void ConnectionPool::scannerConnectionTask() {
for (;;) {
//通过sleep模拟定时效果
this_thread::sleep_for(chrono::seconds(_maxIdleTime));
//扫描整个队列,释放多余的连接
unique_lock<mutex>lock(_queueMutex);
while(_connectionCnt > _initSize) {
Connection* p = _connectionQue.front();
if (p->getAliveTime() >= (_maxIdleTime * 1000)) {
_connectionQue.pop();
_connectionCnt--;
delete p;//释放连接
}
else {
break;//对头的连接都没有超过最大存活时间,后面的连接更不可能超过
}
}
}
}
#pragma once
/*
程序名:Connection.h
程序功能:实现数据库的连接和增删改查操作
作者:dyy
*/
#include <mysql.h>
#include<string>
#include<iostream>
#include<ctime>
using namespace std;
class Connection {
public:
Connection();
~Connection();
bool connect(string ip, unsigned short port, string username, string password, string dbname);//数据库连接函数
bool update(string sql);//数据库更新操作
MYSQL_RES* query(string sql);//数据库查询操作
//刷新一下连接的起始空闲时间点
void refreshAliveTime() {
_alivetime = clock();
}
//返回存活的时间
clock_t getAliveTime() {
return clock() - _alivetime;
}
private:
MYSQL *_conn;//表示和MYSQLServer的一条连接
clock_t _alivetime;//记录进入队列空闲的连接的存活时间
};
/*
程序名:Connection.cpp
程序功能:实现数据库的连接和增删改查操作
作者:dyy
*/
#include"Public.h"
#include "Connection.h"
Connection::Connection() {
//初始化数据库连接
_conn = mysql_init(nullptr);
}
Connection::~Connection() {
//释放数据库连接资源
if (_conn != nullptr) {
mysql_close(_conn);
}
}
bool Connection::connect(string ip, unsigned short port, string username, string password, string dbname) {
//连接数据库
/*MYSQL* p = mysql_real_connect(_conn, ip.c_str(), username.c_str(), password.c_str(), dbname.c_str(), port, nullptr, 0);*/
MYSQL* p = mysql_real_connect(_conn, "127.0.0.1", "root", "12345", "chat", 3306, nullptr, 0);
if (p == nullptr) {
cout << "connection error" << endl;
}
return p != nullptr;
}
bool Connection::update(string sql) {
//更新操作 insert delete update
if (mysql_query(_conn, sql.c_str())) {
LOG("更新失败:" + sql);
cout << mysql_error(_conn) << endl;
return false;
}
return true;
}
MYSQL_RES* Connection::query(string sql) {
//查询操作 select
if (mysql_query(_conn, sql.c_str())) {
LOG("查询失败:" + sql);
return nullptr;
}
return mysql_use_result(_conn);
}
资料来源:施磊老师中型项目--MYSQL数据库连接池。