/***********************************************ConnectionPool*****************************************/
hello~大大大家好!本次主包为大家带来的是连接池学习贴,接下来我们对连接池这个小项目做一些基本的介绍,对了!关于内存池有没有兴趣呢,有的话点开此链接就可以跳转哟~话不多说,正片开始!https://mpbeta.youkuaiyun.com/mp_blog/creation/editor/148906710
一、技术点介绍
本项目不大但是可以为你带来满满干货。本项目主要涉及了MYSQL编程、单例模式设计、queue队列容器、C++11编程、CAS原子特性、生产者消费者模型、线程的同步与互斥、智能指针shared_ptr。(这些是不是看着很多,就像HR看你的简历一样……)。
二、项目背景
为了提高访问数据库的效率,我们引入了连接池。那么为什么它就可以提高访问效率呢?其实,在并发程度或者说访问量不大的场景下,连接池是发挥不了优势的。在访问数据库的过程中,客户端需要发起TCP三次握手------MYSQL验证---------响应操作--------MYSQL释放回收资源--------TCP四次挥手。这一系列过程似乎花不了多长时间,但是在高并发大量的访问场景下,频繁地TCP三次握手四次挥手、连接的验证以及回收是一种极大的浪费且似乎没有必要。那么连接池就应运而生。
服务器大量响应belike::::::::::::::::::::::::::::::::
三、连接池功能点
本项目主要实现连接池的基本功能。包括初始连接量、最大连接量、最大空闲时间和连接超时时间。
连接池运行过程
在没有引入连接池的时候,客户端发起使用mysql进行数据库的访问,其实是客户端与mysql客户端进行连接,再通过MySQL客户端与MySQL服务器进行连接,而客户对数据库的操作实质上是MySQL服务器对数据库的操作。BUT,MySQL客户端与MySQL服务器的连接包含TCP的连接与释放、MySQL之间的验证与回收,占用不少时间。
引入连接池以后,我们预先开辟一些数量的连接类,当客户端需要操作数据库的时候,MySQL客户端就从连接池取一个连接进行与MySQL服务器的连接,跳过了tcp连接与释放、MySQL的验证与回收(其实这些工作只是在初始化连接池操作了一次)。当使用完连接后再将连接放归连接池。
连接池设计功能点
1、连接池只需要一个实例,因此是单例设计模式(因为在客户端申请连接的过程中连接池是从始至终一个就够了
连接需要很多个
2、从连接池中可以获取与MySQL的连接connection
3、空闲连接connection全部放在队列中保存,因为该队列是共享资源,需要进行线程安全
4、如果初始的连接connection用完了,需要动态扩充连接,最大为maxsize
5、队列中超过最大空闲时间的连接就释放掉,最少保留初始数量的连接就足够了
6、如果队列中无可用连接,且超过连接超时时间,那么就判定连接失败
7、用户获取的连接用智能指针来管理,因为智能指针可用做到线程安全且自动回收
8、连接的生产和消费其实是生产者消费者模型
四、开始实现
gogogo~出发喽~
我们开始实现具体类的代码。
代码结构
头文件别忘了
#pragma once
#include <string>
#include <queue>
#include "Connection.h"
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <memory>
using namespace std;
connectionPool
//单例模式实现
class ConnectionPool {
public:
static ConnectionPool* getConnectionPool();
//用户接口,获得连接来与数据库进行交互
shared_ptr<Connection> getConnection();
private:
bool loadConfig();//加载配置文件(包括ip,端口号,MySQL密码等等)
void produceconnections();//产生连接
void cleanconnections();//清理空闲连接
ConnectionPool();//构造函数
//内部属性
string _ip;//ip地址
unsigned int _port;//端口号
string _username;//MySQL用户名
string _password;//MySQL密码
string _dbname;//数据库名字
int _initSize;//初始连接池连接数量
int _maxSize;//最大连接池连接数量
int _maxIdleTime;//最大空闲时间
int _ConnectionTimeOut;//超时连接时间
queue<Connection*> _pool;//连接池(队列存储)
mutex _queueMutex;//互斥锁,保证线程安全
atomic_int _currentSize;//原子型变量,记录当前连接总数,也是保证线程安全
condition_variable _Empty;//同步信号量
};
上面各个变量已经解释的很清楚了,不用我再在外面解释了吧。如果需要记得评论一下……
配置文件(mysqlConfg.ini)
#配置文件
ip=127.0.0.1
port=3306
#这里注意修改为你的用户名
username=root
#这里注意修改为你的密码
password=200319
#这里注意修改为你的数据库名
dbname=chat
initSize=10
maxSize=1024
#单位秒
maxIdleTime=60
#单位毫秒
connectionTimeout=100
我是使用vs2022平台书写的代码,配置文件就命名为mysqConfg.ini即可
日志函数
#pragma once
#include <iostream>
#define LOG(str)\
cout<<__FILE__<<" : "<<__LINE__<<" : "<<\
__TIMESTAMP__<<" : "<<str<<endl;
加载配置文件函数
bool ConnectionPool::loadConfig() {
FILE* fp = fopen("mysqlConfg.ini", "r");
if (fp == NULL) {
LOG("mysqlConfg.ini not found");
return false;
}
while (!feof(fp)) {//如果不是文件末尾就循环执行
char line[1024] = { 0 };//写缓冲区
fgets(line, 1024, fp);
string str = line;
int idx = str.find("=",0);//找“=”的索引,找不到说明不是配置信息就接着找
if (idx == -1) {
continue;
}
int enidx = str.find("\n", idx);
string key = str.substr(0, idx);//配置文件前面的配置信息名字
string value = str.substr(idx + 1, enidx - 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 == "dbname") {
_dbname = 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());
}
return true;
}
}
连接池构造函数
ConnectionPool::ConnectionPool() {
if (!loadConfig()) {
LOG("load mysqlConfg.ini failed");
return;
}
//创建初始连接
for (int i = 0; i < _initSize; i++) {
Connection* conn = new Connection();
conn->connect(_ip, _port, _username, _password, _dbname);
_pool.push(conn);
conn->resetFreeTime();//重置空闲时间(入队列就得重置)
_currentSize++;
}
//启动生产CONN线程
thread producer(bind(&ConnectionPool::produceconnections, this));
producer.detach();
//启动清理线程,将超过最大空闲时间的连接清理掉
thread cleaner(bind(&ConnectionPool::cleanconnections, this));
cleaner.detach();
}
绑定器将成员函数和当前对象绑定,要不然这个成员方法就不能用在这里。
/*************************************************continue*************************************************/
下集预告:
实现生产者消费者线程函数
设计连接类
实现回收创建连接
……
ps:写太多了,剩下大约一半留到下一篇分解。up扛不住了,打会最后生还者去了,记住最后生还者只有第一部,没有第二部!大家加油消化,我先撤了,bye~