C++实战-基于c++11新特性的mysql连接池

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: C++实战-基于c++11新特性的mysql连接池

安装MySQL

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).需要配合 <fstream> 使用

               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 <mysql/mysql.h>
#include <string>
#include <chrono>
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<microseconds>(res);        //将纳秒转成微妙
    return mil.count();
}
#include "MysqlConn.h"
#include <mutex>
#include <condition_variable>
#include <queue>
class ConnectionPool
{
public:
    //对外接口,获取线程池
    static ConnectionPool  *getConnectPool();    //静态局部变量是线程安全的
    //获取线程池中的连接
    shared_ptr<MysqlConn>   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<MysqlConn *>m_connectionQ;    //共享资源
};
#include "ConnectionPool.h"
#include <fstream>
#include <thread>
#include <jsoncpp/json/json.h>
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<m_minSize;++i)
    {
        addConnect();
    }
    //创建子线程用于检测并创建新的连接
    thread producer(&ConnectionPool::produceConnection,this);
    //销毁连接,检测并销毁连接
    thread recycler(&ConnectionPool::recycleConnection,this);
    //设置线程分离
    producer.detach();
    recycler.detach();
}
//子线程-->任务函数
void ConnectionPool::produceConnection()
{
    //通过轮询的方式不断的去检测
    while(true) 
    {
        //操作共享资源,需要加锁
        unique_lock<mutex> 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<mutex> 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<MysqlConn> ConnectionPool::getConnection()
{
    //需要操作共享资源
    unique_lock<mutex> 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<MysqlConn> connptr(m_connectionQ.front(),[this](MysqlConn *conn){
        unique_lock<mutex> locker(m_mutexQ);
        conn->refreshAliveTime();
        m_connectionQ.push(conn);   
    });
    //弹出,放到了队尾
    m_connectionQ.pop();
    m_cond.notify_all();
    return connptr;
}
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7天前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
83 59
|
5天前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
|
6天前
|
架构师 关系型数据库 MySQL
MySQL最左前缀优化原则:深入解析与实战应用
【10月更文挑战第12天】在数据库架构设计与优化中,索引的使用是提升查询性能的关键手段之一。其中,MySQL的最左前缀优化原则(Leftmost Prefix Principle)是复合索引(Composite Index)应用中的核心策略。作为资深架构师,深入理解并掌握这一原则,对于平衡数据库性能与维护成本至关重要。本文将详细解读最左前缀优化原则的功能特点、业务场景、优缺点、底层原理,并通过Java示例展示其实现方式。
16 1
|
14天前
|
SQL 安全 关系型数据库
MySQL8.2有哪些新特性?
【10月更文挑战第3天】MySQL8.2有哪些新特性?
28 2
|
19天前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
19天前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
19天前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1天前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
|
19天前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析继承机制(三)
【C++】面向对象编程的三大特性:深入解析继承机制
|
19天前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析继承机制(二)
【C++】面向对象编程的三大特性:深入解析继承机制