sudo apt-get install mysql-server //mysql服务器端
sudo apt-get install mysql-client //mysql客户端--方便操作
sudo apt-get install libmysqlclient-dev //mysql开发的库
1.如果不使用连接池,每次server端与数据库的交互都需要创建线程和销毁线程。大量的创建线程和销毁线程是消耗系统资源的
2.mysql数据库与服务器端的是通过TCP进行通信的,按照TCP的通信规则,需要建立连接和断开连接,这一过程相对比较慢和繁琐
3.每次连接都需要进行身份验证
通过预先创建一定数量的连接,放到一个池子。当客户端有请求时,服务器端需要与mysql进行交互,那么只需要从池子里取出一个连接,当操作完成再将连接放到连接池中。如此以来避免了频繁的创建和销毁线程。
池子只需要一个就够用了,一个池子里有很多连接了。所以对于线程池这个类而言使用单例模式最为合适。
1.首先我们需要有连接,即我们需要提前准备好池子里的连接。如何实现这些连接呢?
很简单,对libmysqlclient-dev所提供的API进行封装。根据一般需求的话,一个连接应该具备的功能:连接、更新、查询、事务处理、连接的状态(空闲时长)
优化:当数据库使用一段时间后,连接的数目是趋于稳定的,所以我们设计的连接池具备自动管理连接数的功能
2.设计一个池子来存放和管理这些连接
使用队列:
1).队列的特点-->先进先出
2).我们不妨把每次更新后的连接插入到队列的尾部。那么队头部分的连接一定是空闲时长比较久的,如果超过了限定的最大空闲时长,我们就销毁该线程。
3).也可以使用小顶堆,不过没用必要,实现起来还特别麻烦
1.多线程编程
2.线程同步(互斥锁、条件变量)
3.chrono库(处理时间)
4.智能指针
5.lambda表达式
6.单例模式
7.对MySQL数据操作的API进行封装
8.STL容器
9.生产者消费者模型
10.Jsoncpp库
对API的封装 ===> 可以理解为连接池里的一个连接
1.初始化,获得一个操作数据库实例的对象(面向对象编程)
MYSm_conn = mysql_init(nullptr); //返回一个MYSQL对象
mysql_set_character_set(m_conn,"utf8"); //设置编码
2.连接指定的数据库:数据库名、IP、用户名、密码等信息 ===> 用户提供的参数
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag)
3.数据库相关操作
更新:增加、删除、插入 int mysql_query(MYSQL *mysql, const char *q)
查询:执行mysql语句 int mysql_query(MYSQL *mysql, const char *q)
得到结果集 MYSQL_RES *mysql_store_result(MYSQL *mysql)
获取一行的内容 MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
事务处理:事务的操作、事务的回滚、事务提交
my_bool mysql_autocommit(MYSQL *mysql, my_bool auto_mode)
my_bool mysql_commit(MYSQL *mysql)
my_bool mysql_rollback(MYSQL *mysql)
设计连接池 ==> 管理上面封装好的连接
1.因为池子只需要一个,所以采用单例模式
何为单例模式:单例模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的唯一的对象。该对象外界可以获取到,但是不能创建和拷贝。
通俗的讲:单例模式就是说每一个类只有单一的一个对象,并且这个对象由该对象自己创建。如果让外部调用构造函数或者拷贝构造函数等,很难保证外部只创建了一个实例对象。为了让外部获取到
2.使用生产者消费者模型:三要素(生产者、消费者、容器)
因为多线程并且存在资源竞争关系,肯定会涉及到线程同步的问题
解决线程同步问题:
只使用互斥锁
使用条件变量+互斥锁:有效使用条件变量可以避免当前队列为空时,消费者之间无意义的竞争,当队列满时生产者之间无意义的竞争。
3.通过配置文件来读取连接MySQL数据库的信息
XXXconfig.json ==> 使用jsoncpp库解析json文件
具体步骤:
1).安装Jsoncpp第三方库:sudo apt-install Jsoncpp-dev
2).需要配合
使用 3).读配置文件
定义 Reader对象 rd
定义 Value对象 root
将ifs指向的json文件中的内容存放到root中,root是一个二维数组
判断是否构建成功: root.isobject()
root[key] 获取对应的值 eg: m_username = root["username"];
4.对外提供一个获取连接的接口
外部需要从连接池中获取连接,并通过该连接对数据库进行操作
如何设计该对外接口:
使用智能指针作为返回值,因为获取连接的是地址,通过指针传递可以节省时间
智能指针可以帮助我们自动释放连接,这个连接可能被多个工作线程获取到,使用shared_ptr会维护一个引用计数,当引用计数为0时,会自动释放指针指向的内存空间。防止使用者忘记释放内存,导致内存泄漏。
函数内部实现: 其实是消费者从队列中取产品的过程
c++11新特性,使用模板unique_lock来包装mutex,自动加锁和解锁。创建时加锁,析构时解锁。一般是局部变量,所以会及时析构
c++11新特性,使用wait_for等待一定时长,如果等待一定时长后队列还是为空,则不再阻塞等待,再次判断队列是否为空(轮询的方式)
使用lambda返回获取的连接,并在函数体内部更新该连接的空闲时间的起始点
5.连接池实现自动管理连接数(不够时创建连接,销毁空闲时间较长的连接)
通过两个子线程,因为主线程不能阻塞在这个地方,所以通过两个子线程取完成,并设置线程分离
不够时自动创建连接:
线程池维护了两个数据,一个最小连接数,一个最大连接数
销毁空闲时间较长的连接:
从队头取出连接,并将该连接的空闲时长与设定的最大空闲时长比较,如果大于等于就从队列中弹出并销毁
#pragma one #include
#include #include using namespace std::chrono_literals; using namespace std; using namespace chrono; class MysqlConn { public: //创建一个MYSQL实例对象并设置字符集 MysqlConn(); //是放在资源 ~MysqlConn(); //连接指定的数据库 bool connect(string ip,string user,string passwd,string bdName,unsigned int port); //更新:增加、删除、修改 bool undate(string sql); //查询 bool query(string sql); //遍历得到的结果集 bool next(); //获取结果集里的值 string value(int index); //事务处理提交方式 bool transaction(); //事务提交 bool commit(); //事务回滚 bool rollback(); //更新空闲时间点 void refreshAliveTime(); //计算连接空闲时长 long long getAliveTime(); private: //每次搜索都需要更新结果集 void freeResult(); MYSQL *m_conn = nullptr; MYSQL_RES *m_result = nullptr; MYSQL_ROW m_row; steady_clock::time_point m_aliveTime; }; #include "MysqlConn.h" MysqlConn::MysqlConn() { //获取一个MYSQL句柄 m_conn = mysql_init(nullptr); //设置字符集 mysql_set_character_set(m_conn,"utf8"); } MysqlConn:: ~MysqlConn() { if(m_conn != nullptr) { mysql_close(m_conn); } freeResult(); } bool MysqlConn::connect(string ip,string user,string passwd,string dbName,unsigned int port) { /* MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag) */ MYSQL *p = mysql_real_connect(m_conn,ip.c_str(),user.c_str(),passwd.c_str(),dbName.c_str(),port,nullptr,0); return p != nullptr; } bool MysqlConn::undate(string sql) { if(mysql_query(m_conn,sql.c_str())) { return false; } return true; } bool MysqlConn::query(string sql) { freeResult(); if(mysql_query(m_conn,sql.c_str())) { return false; } //得到结果集 m_result = mysql_store_result(m_conn); return true; } bool MysqlConn::next() { if(m_result != nullptr) { m_row = mysql_fetch_row(m_result); //获取一行 if(m_row != nullptr) { return true; } } return false; } string MysqlConn::value(int index) { int rowCount = mysql_num_fields(m_result); //返回结果集中字段数目 if(index >= rowCount || index < 0) { return string(); } char* ans = m_row[index]; unsigned long length = mysql_fetch_lengths(m_result)[index]; return string(ans,length); } bool MysqlConn::transaction() { return mysql_autocommit(m_conn,false); //自动提交改为自动提交 } bool MysqlConn::commit() { return mysql_commit(m_conn); } bool MysqlConn::rollback() { return mysql_rollback(m_conn); } void MysqlConn::freeResult() { if(m_result) { mysql_free_result(m_result); m_result = nullptr; } } void MysqlConn::refreshAliveTime() { m_aliveTime = steady_clock::now(); } //计算连接空闲时长 long long MysqlConn::getAliveTime() { nanoseconds res = steady_clock::now() - m_aliveTime; //nanosecods 纳秒 milliseconds mil = duration_cast
(res); //将纳秒转成微妙 return mil.count(); } #include "MysqlConn.h" #include
#include #include class ConnectionPool { public: //对外接口,获取线程池 static ConnectionPool *getConnectPool(); //静态局部变量是线程安全的 //获取线程池中的连接 shared_ptr getConnection(); //防止外界通过拷贝构造函数和移动拷贝构造函数 ConnectionPool(const ConnectionPool &obj) = delete; ConnectionPool& operator=(const ConnectionPool& obj) = delete; ~ConnectionPool(); private: //构造函数私有化 ConnectionPool(); //解析配置文件 bool parseJsonFile(); //任务函数 void produceConnection(); //生产数据库连接 void recycleConnection(); //销毁数据库连接 void addConnect(); //添加连接 private: string m_user; string m_passwd; string m_ip; string m_dbName; unsigned short m_port; //连接的上限和下限,自动维护线程池的连接数 int m_minSize; int m_maxSize; //连接的超时时长 int m_timeout; int m_maxIdleTime; //线程同步 mutex m_mutexQ; //互斥锁 condition_variable m_cond; //条件变量 queue m_connectionQ; //共享资源 }; #include "ConnectionPool.h" #include
#include #include bool ConnectionPool::parseJsonFile() { //获取配置文件 fstream ifs("../dbconf.json"); //读配置文件 Json::Reader rd; Json::Value root; rd.parse(ifs,root); //解析配置文件 if(root.isObject()) { m_ip = root["ip"].asString(); m_user = root["username"].asString(); m_passwd = root["password"].asString(); m_dbName = root["dbName"].asString(); m_port = root["port"].asInt(); m_minSize = root["minSize"].asInt(); m_maxSize = root["maxSize"].asInt(); m_timeout = root["timeout"].asInt(); m_maxIdleTime = root["maxTdleTime"].asInt(); return true; } return false; } void ConnectionPool::addConnect() { MysqlConn *conn = new MysqlConn; /* bool MysqlConn::connect(std::string ip, std::string user, std::string passwd, std::string bdName, unsigned int port) */ conn->connect(m_ip,m_user,m_passwd,m_dbName,m_port); conn->refreshAliveTime(); m_connectionQ.push(conn); } ConnectionPool::ConnectionPool() { //加载配置文件 if(!parseJsonFile()) { return; } //创建最少连接数 for(int i=0;i 任务函数 void ConnectionPool::produceConnection() { //通过轮询的方式不断的去检测 while(true) { //操作共享资源,需要加锁 unique_lock locker(m_mutexQ); //判断连接数是否达到容量,如果大于等于容量则需要阻塞一段时间 while (m_connectionQ.size() >= m_maxSize) { m_cond.wait(locker); } addConnect(); m_cond.notify_all(); //唤醒消费者 } } void ConnectionPool::recycleConnection() { while(true) { //休眠一定的时长 this_thread::sleep_for(chrono::milliseconds(500)); unique_lock locker(m_mutexQ); //让线程池中最少保持用于 m_minSize个线程 while(m_connectionQ.size() > m_minSize) { MysqlConn *recyConn = m_connectionQ.front(); //如果超时则销毁 if(recyConn->getAliveTime() >= m_maxIdleTime) { m_connectionQ.pop(); delete recyConn; } else { break; } } } } ConnectionPool::~ConnectionPool() { while(!m_connectionQ.empty()) { MysqlConn *conn = m_connectionQ.front(); m_connectionQ.pop(); delete conn; } } //对外接口,获取线程池 ConnectionPool* ConnectionPool::getConnectPool() { static ConnectionPool pool; return &pool; } //获取线程池中的连接 shared_ptr ConnectionPool::getConnection() { //需要操作共享资源 unique_lock locker(m_mutexQ); //判断连接池队列为空 while(m_connectionQ.empty()) { if(cv_status::timeout == m_cond.wait_for(locker,chrono::milliseconds(m_timeout))) { if(m_connectionQ.empty()) { continue; } } } //自定义shared_ptr析构方法,重新将连接放回到连接池中,而不是销毁 shared_ptr connptr(m_connectionQ.front(),[this](MysqlConn *conn){ unique_lock locker(m_mutexQ); conn->refreshAliveTime(); m_connectionQ.push(conn); }); //弹出,放到了队尾 m_connectionQ.pop(); m_cond.notify_all(); return connptr; }