c++ 数据库连接池(二)

MysqlConnectionPool 类总结

类的作用

  • MysqlConnectionPool 是一个数据库连接池管理类,通过单例模式实现。
  • 它通过管理一个连接队列来优化数据库连接的使用,减少频繁建立和销毁连接的开销。
  • 使用生产者-消费者模型
    • 生产者:负责创建新的数据库连接(produceConnect)。
    • 消费者:通过 getConnection 方法从连接池中获取可用连接。
    • 定期清理多余的或超时的连接(dropConnection)。

单例模式

  • 通过单例模式保证全局只有一个连接池实例,防止多个连接池实例管理的连接资源冲突。

类的主要功能

1. 单例模式

  • 目的
    • 确保连接池全局只有一个实例,统一管理所有数据库连接。
  • 实现方法
    • 构造函数设置为 private,避免外部直接实例化。
    • 提供一个静态方法 instance() 返回唯一实例。
    • 禁用拷贝构造和赋值操作(delete 拷贝构造函数和赋值操作符)。

2. 配置文件解析

  • 方法bool parseJsonFile()
    • 从 JSON 文件中读取数据库连接的配置信息,包括用户名、密码、数据库名称、主机地址、端口号、最小连接数、最大连接数、超时时间等。
    • 这些配置用于初始化和管理连接池。

3. 生产者线程

  • 方法void produceConnect()
    • 当连接池中的连接数小于最小连接数时,负责创建新的数据库连接。
    • 将创建的连接添加到队列中,供消费者使用。

4. 消费者线程

  • 方法std::shared_ptr<MysqlConn> getConnection()
    • 从连接池中获取一个可用的数据库连接。
    • 使用 std::shared_ptr 管理连接的生命周期,并通过自定义删除器将连接归还到池中。

5. 删除多余或超时连接

  • 方法void dropConnection()
    • 定期检查连接池中的空闲连接。
    • 删除空闲时间超过最大空闲时间的连接,以节约资源。

6. 创建连接

  • 方法void addConnection()
    • 用于创建一个新的数据库连接并加入到队列中。

私有成员变量

变量名 类型 描述
m_user std::string 数据库用户名。
m_passwd std::string 数据库密码。
m_dbName std::string 数据库名称。
m_host std::string 数据库主机地址(如 127.0.0.1localhost)。
m_port unsigned short 数据库端口号(如 MySQL 默认的 3306)。
m_minSize int 连接池的最小连接数,确保池中始终有足够的空闲连接。
m_maxSize int 连接池的最大连接数,防止连接数无限增长导致资源耗尽。
m_timeout int 消费者获取连接的最大等待时间(单位:毫秒)。
m_maxIdleTime int 连接的最大空闲时间(单位:秒),超过此时间未使用的连接将被删除。
m_SQLConnectQ std::queue<MysqlConn*> 用于存储所有空闲的数据库连接的队列。
m_mutex std::mutex 用于保护 m_SQLConnectQ 的互斥锁,保证线程安全。
m_cond std::condition_variable 用于连接池的线程间同步,消费者等待连接,生产者通知。

类的主要方法

方法名 类型 描述
MysqlConnectionPool() explicit 私有构造函数,用于初始化连接池(如读取 JSON 配置)。
~MysqlConnectionPool() ~MysqlConnectionPool 析构函数,释放所有空闲连接,清空队列。
instance() static 获取连接池的唯一实例。
getConnection() std::shared_ptr<MysqlConn> 消费者调用,从连接池中获取一个可用的数据库连接。
produceConnect() void 生产者调用,生成新的连接并加入队列。
addConnection() void 创建新的数据库连接,初始化后加入连接队列。
dropConnection() void 删除多余的或空闲超时的连接,释放资源。
parseJsonFile() bool 从 JSON 文件读取数据库配置参数。

线程安全的设计

  1. 使用 std::mutex

    • 保护连接队列 m_SQLConnectQ 的操作,避免多个线程同时修改队列引发的竞争问题。
  2. 使用 std::condition_variable

    • 通过生产者-消费者模型协调线程,确保消费者在队列为空时等待,生产者在添加新连接后唤醒消费者。
  3. 通过 std::shared_ptr 管理连接

    • 使用自定义删除器在连接被销毁时自动归还到连接池,避免手动管理的麻烦。

典型的工作流程

  1. 启动连接池

    • 通过 MysqlConnectionPool::instance() 获取单例实例。
    • 从 JSON 配置文件中读取数据库参数,并初始化连接池。
  2. 生产连接

    • 在连接池空闲连接数不足时,生产者线程调用 produceConnect(),创建新的数据库连接并加入队列。
  3. 消费连接

    • 需要数据库连接的线程调用 getConnection(),从队列中取出一个连接。
    • 使用 std::shared_ptr 管理连接,确保连接使用完毕后自动归还到池中。
  4. 清理连接

    • 定期调用 dropConnection(),删除空闲时间过长的连接,保持资源高效利用。

优点

  1. 资源复用

    • 通过连接池重复利用连接,避免频繁创建和销毁数据库连接带来的开销。
  2. 线程安全

    • 使用互斥锁和条件变量,保证多线程操作的安全性。
  3. 可扩展性

    • 支持从配置文件动态调整连接池的参数(如最小/最大连接数、超时时间等)。
  4. 自动化管理

    • 通过生产者-消费者模型,动态管理连接池的连接数,自动回收超时连接。

总结

MysqlConnectionPool 是一个基于单例模式的数据库连接池类,使用生产者-消费者模型动态管理连接。它通过线程安全的队列管理空闲连接,支持高效的数据库操作,同时通过 JSON 配置文件实现灵活的参数调整。这个类适用于高并发场景,能够有效提高系统性能并简化数据库连接管理。

代码

MysqlConnectionPool 类头文件

为了配置服务器一起关闭和开启数据库连接池,我们增加了start和stop函数,连接池的初始化放在了start函数,新增了m_stop标志。
当服务器关闭时,也需要关闭连接池。详情见代码。


/**
  ******************************************************************************
  * @file           : MysqlConnectionPool.h
  * @author         : sally
  * @brief          : None
  * @attention      : None
  * @date           : 25-1-28
  ******************************************************************************
  */


#ifndef MYSQLCONNECTIONPOOL_H
#define MYSQLCONNECTIONPOOL_H
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <string>

#include "MysqlConn.h"

//单例模式
//这个类的作用就是通过一个队列来管理多个连接
//采用生成者-消费者模型,一个线程来创建连接,同时一个线程来删除多余的且超时的连接。
//消费者通过getConnection来获取连接。
//同时数据库的配置信息使用json来配置并从文件中读取。
class MysqlConnectionPool
{
public:
    MysqlConnectionPool(const MysqlConnectionPool&) = delete;
    ~MysqlConnectionPool();
    MysqlConnectionPool& operator =(const MysqlConnectionPool&) = delete;
    static MysqlConnectionPool& instance();
    std::shared_ptr<MysqlConn> getConnection();

private:
    void produceConnect();
    void addConnection();
    void dropConnection();
    bool parseJsonFile();

private:
    explicit MysqlConnectionPool();
    std::string m_user;
    std::string m_passwd;
    std::string m_dbName;
    std::string m_host;
    unsigned short m_port;
    int m_minSize;
    int m_maxSize;
    int m_timeout;
    int m_maxIdleTime;
    std::queue<MysqlConn*> m_SQLConnectQ;
    std::mutex m_mutex;
    std::condition_variable m_cond;
};


#endif //MYSQLCONNECTIONPOOL_H

MysqlConnectionPool 类源文件

/**
  ******************************************************************************
  * @file           : MysqlConnectionPool.cpp
  * @author         : sally
  * @brief          : None
  * @attention      : None
  * @date           : 25-1-28
  ******************************************************************************
  */


#include "MysqlConnectionPool.h"

#include <config.h>
#include <jsoncpp/json/json.h>
#include <fstream>
#include <thread>

MysqlConnectionPool::~MysqlConnectionPool()
{
    while (!m_SQLConnectQ.empty())
    {
        MysqlConn* conn = m_SQLConnectQ.front();
        m_SQLConnectQ.pop();
        delete conn;
    }
}

MysqlConnectionPool& MysqlConnectionPool::instance()
{
    static MysqlConnectionPool mysqlConnectPool;
    return mysqlConnectPool;
}

/**
 * 这是消费者
 * @return 返回数据库连接指针
 */
std::shared_ptr<MysqlConn> MysqlConnectionPool::getConnection()
{
    //加锁
    std::unique_lock<std::mutex> lock(m_mutex);
    while (m_SQLConnectQ.empty())
    {
        //如果是空,则需要等待
        if (std::cv_status::timeout == m_cond.wait_for(lock, std::chrono::milliseconds(m_timeout)))
        {
            //如果是超时的话,就继续判断是否为空
            if (m_SQLConnectQ.empty())
                continue;
            //如果不是空的,则会跳出while循环的
        }
    }

    std::shared_ptr<MysqlConn> sqlConnectPtr(m_SQLConnectQ.front(), [this](MysqlConn* conn)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        conn->refreshAliveTime();
        m_SQLConnectQ.push(conn);
    });

    //需要弹出
    m_SQLConnectQ.pop();
    //唤醒其它在等待的线程,因为当有一个线程进入到wait_for时,会释放掉锁,然后其它的线程也可能进入wait_for了。
    m_cond.notify_all();
    return sqlConnectPtr;
}

/**
 * 使用json来解析json文件的步骤:
 *  1、读取文件
 *  2、创建json解析器
 *  3、创建存储解析后的json数据结构(对象)
 *  4、解析
 */
void MysqlConnectionPool::start()
{
    m_stop = false;
    if (!parseJsonFile())
    {
        OPERATION_FAIL("CA_EVP_PriKey is nullptr", "CertificateIssuance", __FILE__, __LINE__, "");
        throw std::runtime_error("Failed to read database configuration information");
    }

    while (m_SQLConnectQ.size() < m_minSize)
    {
        addConnection();
    }

    std::thread producer(&MysqlConnectionPool::produceConnection, this);
    std::thread droper(&MysqlConnectionPool::dropConnection, this);
    producer.detach();
    droper.detach();
}

void MysqlConnectionPool::stop()
{
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_stop = true;
    }
    m_cond.notify_all();
}

/**
 * 这个函数的作用就是添加数据库连接,连接池里面存放的是空闲的数据库连接,当某个连接被取出时,连接队列的连接数量就减1了。
 * 最小的空闲连接池是100,超过100就不添加数据库连接了。
 */
void MysqlConnectionPool::produceConnection()
{
    //
    while (!m_stop)
    {
        std::unique_lock<std::mutex> lock(m_mutex);

        m_cond.wait(lock, [this] { return m_stop || m_SQLConnectQ.size() < m_minSize; });

        if (m_stop)
        {
            //这里说明是要退出时唤醒的,就退出就行
            break;
        }
        //唤醒之后就会进入到这里,到这里唤醒有两种情况一种是退出时的唤醒,一种是是取出了连接,或者连接被删除时这两种情况导致的空闲线程不足了。
        //小于m_minSize,则需要添加连接池
        if (m_SQLConnectQ.size() < m_minSize)
        {
            addConnection();
            //添加完毕,需要唤醒等待数据库连接的线程
            m_cond.notify_all();
        }
    }
}

void MysqlConnectionPool::addConnection()
{
    MysqlConn* conn = new MysqlConn();
    //连接数据库
    if (conn->connect(m_user, m_passwd, m_dbName, m_host, m_port))
    {
        conn->refreshAliveTime();
        m_SQLConnectQ.push(conn);
        std::cout << "102 m_SQLConnectQ.size : " << m_SQLConnectQ.size() << std::endl;
    }
    else
    {
        delete conn;
    }
}

/**
 * 这个函数的作用就是用来删除空闲时间大于最大空闲时间的连接。
 * 一个连接从最后一次活动时间到现在这段时间就是空闲时间。
 */
void MysqlConnectionPool::dropConnection()
{
    while (!m_stop)
    {
        //如果空闲的连接数大于了最小的空闲连接数,说明空闲连接多起来了,
        std::unique_lock<std::mutex> lock(m_mutex);
        m_cond.wait_for(lock, std::chrono::milliseconds(500),
                        [this]() { return m_stop || m_SQLConnectQ.size() > m_minSize; });

        if (m_stop)
            break;

        //只有当连接大于最小的连接时,才会开始删除多余的连接,最小的连接用来保证连接数的呀
        //清理空闲的线程
        //获取一个连接
        //这里再次检查,避免 `wait_for()` 误触发导致错误删除连接
        if (m_SQLConnectQ.size() > m_minSize)
        {
            MysqlConn* conn = m_SQLConnectQ.front();
            if (conn->getAliveTime() >= m_maxIdleTime)
            {
                m_SQLConnectQ.pop();
                delete conn;
                std::cout << "drop mysql connect, m_SQLConnectQ size is: " << m_SQLConnectQ.size() << std::endl;

                //删除后通知 `produceConnection()` 线程,让它检查是否需要创建新连接
                m_cond.notify_all();
            }
        }
    }
}

bool MysqlConnectionPool::parseJsonFile()
{
    //打开json文件
    std::ifstream inputFile("../../src/VideoServer/utils/dbconfig.json", std::ios_base::in);

    Json::Reader rd; //一个 JSON 解析器对象
    Json::Value root; //用于存储解析后的 JSON 数据结构,类似于一个树形结构的根节点。类似于proto里面的对象

    //这里就是解析文件到root数据结构中
    rd.parse(inputFile, root);

    inputFile.close();
    //判断root是不是一个json对象
    if (root.isObject())
    {
        m_host = root["host"].asString();
        m_port = root["port"].asInt();
        m_user = root["userName"].asString();
        m_dbName = root["dbName"].asString();
        m_passwd = root["password"].asString();
        m_minSize = root["minSize"].asInt();
        m_maxSize = root["maxSize"].asInt();
        m_maxIdleTime = root["maxIdleTime"].asInt();
        m_timeout = root["timeout"].asInt();
        return true;
    }

    return false;
}

posted @   吴海琼  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示