c++实现数据库连接池

慈云数据 2024-05-09 技术支持 55 0

介绍

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

c++实现数据库连接池
(图片来源网络,侵删)

连接池原理

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

组成部分:

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

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

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 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 connQue_;       // 存储空闲连接的队列
    std::mutex queueMtx_;              // 保护队列的互斥锁
    std::condition_variable cv_;       //条件变量
    std::atomic connectCnt_; //记录连接的总数,且是线程安全的
};
ConnectPool& ConnectPool::GetInstance() {
    static ConnectPool pool; //静态局部变量的初始化是线程安全的
    return pool;
}
bool ConnectPool::LoadConfig() {
    CSimpleIniA ini;
    if (ini.LoadFile("mysql.conf") connect(ip_, port_, username_, password_, database_)) {
                break;
            }
        }
        //出循环就一定连接上了
        connQue_.push(p);
        p->refreshAliveTime();
        ++connectCnt_;
    }
    //创建一个生产者线程,等待连接不够请求再创建的请求
    std::thread produce([&](){
        while (1) {
            std::unique_lock lock(queueMtx_);
            while (!connQue_.empty()) {//说明初始连接数都没用完
                cv_.wait(lock);
            }
            //被唤醒说明初始连接数用完且不够了,拿到锁开始生产
            if (connectCnt_ 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 lock(queueMtx_);
            while (connectCnt_ > initSize_) {
                //队首元素的存活时间最长,若它都没超过最大空闲时间,则可以不用判断了
                MySql* p = connQue_.front();
                if (p->GetAliveTime() 归还连接
    std::unique_ptr sp(connQue_.front(), [&](MySql* p){
        std::unique_lock lock(queueMtx_);
        connQue_.push(p);
        p->refreshAliveTime();
    });
    connQue_.pop();
    //消费后若队列空,则通知生产者
    if (connQue_.empty()) {
        cv_.notify_all();
    }
    return sp;
}
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon