c++实现数据库连接池

介绍

为提高mysql的访问性能,可增加连接池。为什么他能提高性能: mysql是基于C/S架构,从客户端到服务器,一条sql的执行流程:tcp三次握手->mysql连接认证->执行sql->关闭mysql连接->tcp四次挥手 每次数据库都需要这5步太耗时,连接池缓存连接,后续直接用,5步变1步。

连接池原理

一般设置成单例,用一个队列存放所有的空闲连接。

组成部分:

bash 复制代码
1.  认证所需的信息
2. 初始连接数:创建单例时,构造函数就会创建初始连接数量的连接,以供使用
3. 最大连接数:存在的连接数不能超过它
4. 连接超时时间:当获取一条连接花费的时间超过此时间,则返回(如连接数达到最大了,又没有人释放)
5. 最大空闲时间:当连接在队列里存活的时间超过了此时间,且连接数>初始连接数,就删除多余连接直至初始连接数

代码示例,完整代码见https://github.com/1412771048/connect_pool

cpp 复制代码
#pragma once
#include <string>
#include <queue>
#include <mutex>
#include <atomic>
#include <thread>
#include <memory>
#include <condition_variable>
#include <functional>

#include "mysql.hpp"
#include "SimpleIni.h"

//连接池类
class ConnectPool {
public:
    ConnectPool(const ConnectPool&) = delete;
    ConnectPool(ConnectPool&&) = delete;
    ConnectPool& operator=(const ConnectPool&) = delete;
    ConnectPool& operator=(ConnectPool&&) = delete;
    ~ConnectPool() = default;

    static ConnectPool& GetInstance();
    //给外部提供的接口:从连接池获取一条连接,且用智能指针管理,自定义删除器,使其析构时归还连接而不是释放连接
    std::unique_ptr<MySql, std::function<void(MySql*)>> GetConnection(); 
private:
    ConnectPool();               // 构造函数私有化,单例
    bool LoadConfig();           // 加载配置文件

    std::string ip_;             // mysql的ip
    uint16_t port_;              // mysql的port
    std::string username_;       // 登录mysql的用户名 
    std::string password_;       // 登录mysql的密码
    std::string database_;       // 要访问的数据库名
    uint32_t initSize_;          // 初始连接数
    uint32_t maxSize_;           // 最大连接数
    uint16_t maxIdleTime_;       // 最大空闲时间 s
    int connectTimeout_;         // 获取连接的超时时间 ms

    std::queue<MySql*> connQue_;       // 存储空闲连接的队列
    std::mutex queueMtx_;              // 保护队列的互斥锁
    std::condition_variable cv_;       //条件变量
    std::atomic<uint16_t> connectCnt_; //记录连接的总数,且是线程安全的
};

ConnectPool& ConnectPool::GetInstance() {
    static ConnectPool pool; //静态局部变量的初始化是线程安全的
    return pool;
}

bool ConnectPool::LoadConfig() {
    CSimpleIniA ini;
    if (ini.LoadFile("mysql.conf") < 0) {
        return false;
    }
    ip_ = ini.GetValue("mysql", "ip");
    port_ = std::stoi(ini.GetValue("mysql", "port"));
    username_ = ini.GetValue("mysql", "username");
    password_ = ini.GetValue("mysql", "password");
    database_ = ini.GetValue("mysql", "database");
    initSize_ = std::stoi(ini.GetValue("mysql", "initSize"));
    maxSize_ = std::stoi(ini.GetValue("mysql", "maxSize"));
    maxIdleTime_ = std::stoi(ini.GetValue("mysql", "maxIdleTime"));
    connectTimeout_ = std::stoi(ini.GetValue("mysql", "connectTimeout"));
    return true;
}

ConnectPool::ConnectPool() {
    if (!LoadConfig()) {
        return;
    }
    //连接池第一次时先创建初始数量的连接供外部使用
    for (int i = 0; i < initSize_; ++i) {
        MySql* p = new MySql;
        while (1) {
            if (p->connect(ip_, port_, username_, password_, database_)) {
                break;
            }
        }
        //出循环就一定连接上了
        connQue_.push(p);
        p->refreshAliveTime();
        ++connectCnt_;
    }

    //创建一个生产者线程,等待连接不够请求再创建的请求
    std::thread produce([&](){
        while (1) {
            std::unique_lock<std::mutex> lock(queueMtx_);
            while (!connQue_.empty()) {//说明初始连接数都没用完
                cv_.wait(lock);
            }
            //被唤醒说明初始连接数用完且不够了,拿到锁开始生产
            if (connectCnt_ < maxSize_) {
                MySql* p = new MySql;
                while (1) {
                    if (p->connect(ip_, port_, username_, password_, database_)) {
                        break;
                    }
                }
                //出循环就一定连接上了
                connQue_.push(p);
                p->refreshAliveTime();
                ++connectCnt_;
            }
            //通知等待的消费者们
            cv_.notify_all();
        }
    });//函数较短就原地写,长就拆走,也可用std::bind
    produce.detach();

    //开一个线程专门扫描超过最大空闲时间,进行连接回收
    std::thread scan([&](){
        while (1) {
            //用sleep模拟定时,每次睡一个最大空闲时间
            std::this_thread::sleep_for(std::chrono::seconds(maxIdleTime_));
            //可能会操作队列,要加锁
            std::unique_lock<std::mutex> lock(queueMtx_);
            while (connectCnt_ > initSize_) {
                //队首元素的存活时间最长,若它都没超过最大空闲时间,则可以不用判断了
                MySql* p = connQue_.front();
                if (p->GetAliveTime() < (maxIdleTime_ * 1000)) {
                    break;
                } 
                connQue_.pop();
                --connectCnt_;
                delete p;
            }
        }
    });
    scan.detach();
}

std::unique_ptr<MySql, std::function<void(MySql*)>> ConnectPool::GetConnection() {
    std::unique_lock<std::mutex> lock(queueMtx_);
    if (connQue_.empty()) {
        //等待,时间若未被唤醒,也自动醒
        cv_.wait_for(lock, std::chrono::milliseconds(connectTimeout_));
        if (connQue_.empty()) {
            return nullptr; //获取连接超时
        }
    }
    //能走到这:要么队列不为空,要么队列为空被唤醒后不为空
    //自定义删除器,当客户端调用此函数获取连接,用完后,智能指针析构->归还连接
    std::unique_ptr<MySql, std::function<void(MySql*)>> sp(connQue_.front(), [&](MySql* p){
        std::unique_lock<std::mutex> lock(queueMtx_);
        connQue_.push(p);
        p->refreshAliveTime();
    });
    connQue_.pop();
    //消费后若队列空,则通知生产者
    if (connQue_.empty()) {
        cv_.notify_all();
    }
    return sp;
}
相关推荐
van叶~25 分钟前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法
knighthood200136 分钟前
解决:ros进行gazebo仿真,rviz没有显示传感器数据
c++·ubuntu·ros
半盏茶香1 小时前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
小堇不是码农1 小时前
在VScode中配置C_C++环境
c语言·c++·vscode
Jack黄从零学c++1 小时前
C++ 的异常处理详解
c++·经验分享
捕鲸叉7 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer7 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq7 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
青花瓷8 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
幺零九零零10 小时前
【C++】socket套接字编程
linux·服务器·网络·c++