WebServer -- 数据库连接池

目录

🎂基础知识

🚩整体内容

🌼单例模式创建

🎂连接池(代码实现)

初始化

获取 && 释放连接

销毁连接池

🍑RAII 机制释放数据库连接

定义

实现


🎂基础知识

什么是数据库连接池?

池是一组资源的集合,这组资源在服务器启动之初,就被完全创建好并初始化

通俗来说,池是资源的容器,本质上是对资源的复用

顾名思义,连接池中的资源为一组数据库连接,由程序动态的对池中的连接进行使用 / 释放

当系统开始处理客户请求时,如果它需要相关的资源,可以直接从池中获取,无需动态分配;

当服务器处理完一个客户连接后,可以把相关资源放回池中,无需执行系统调用释放资源

数据库访问的一般流程是什么?

当系统需要访问数据库时,先系统创建数据库连接,完成数据库操作,然后系统断开数据库连接

为什么要创建连接池?

从一般流程看,若系统需要频繁访问数据库,则需要频繁创建和断开数据库连接,

而创建数据库连接,是一个很耗时的操作,也容易对数据库造成安全隐患

在程序初始化的时候,集中创建多个数据库连接,并把它们集中管理,供程序使用,可以保证较快的数据库读写速度,更加安全可靠

🚩整体内容

概述

池可以看作资源的容器,有多种实现方法:数组,链表,队列...

这里,使用 单例模式 和 链表 创建数据库连接池,实现对数据库连接资源的复用

TinyWebserver 中,数据库模块分 2 部分:

1)数据库连接池的定义

2)利用连接池完成登录注册的校验功能

具体地,工作线程从数据库连接池取得一个连接,访问数据库中的数据,访问完毕后将连接交还连接池

内容

本博客介绍数据库连接池的定义,具体涉及到:单例模式的创建,连接池代码的实现,RAII 机制释放数据库连接

  • 单例模式创建
    描述连接池的单例实现
  • 连接池代码实现
    对连接池的外部外文接口的理解
  • RAII 机制释放数据库连接
    描述连接释放的封装逻辑

🌼单例模式创建

使用局部静态变量懒汉模式创建连接池

class connection_pool
{
public:
    // 局部静态变量单例模式
    static connection_pool *GetInstance();

private:
    connection_pool();
    ~connection_pool();
};

// 类外实现
connection_pool *connection_pool::GetInstance()
{
    static connection_pool connPool;
    return &connPool;
}

🎂连接池(代码实现)

连接池的定义中注释比较详细,这里仅对其实现进行解析

连接池的功能:初始化,获取连接,释放连接,销毁连接池

初始化

值得注意的是,销毁连接池没有直接被外部调用,而是通过 RAII 机制来完成自动释放;

使用信号量实现多线程争夺连接的同步机制,这里将信号量初始化为数据库的连接总数

// 构造函数,初始化连接池中的连接数量
connection_pool::connection_pool()
{
    this->CurConn = 0; // 当前连接数
    this->FreeConn = 0; // 空闲连接数
}

// 析构函数,销毁连接池
connection_pool::~connection_pool()
{
    DestroyPool(); // 销毁连接池
}

// 初始化连接池
void connection_pool::init(string url, string User, string PassWord,
                           string DBName, int Port, unsigned int MaxConn)
{
    // 初始化数据库信息
    this->url = url; // 数据库地址
    this->Port = Port; // 数据库端口
    this->User = User; // 用户名
    this->PassWord = PassWord; // 密码
    this->DatabaseName = DBName; // 数据库名称

    // 创建 MaxConn 条数据库连接
    for (int i = 0; i < MaxConn; i++) {
        MYSQL *con = NULL; // MySQL 连接指针
        con = mysql_init(con); // 初始化连接

        if (con == NULL) {
            cout << "Error:" << mysql_error(con); // 输出错误信息
            exit(1); // 退出程序
        }
        con = mysql_real_connect(con, url.c_Str(), User.c_str(),
                                 DBName.c_str(), Port, NULL, 0); // 连接数据库

        if (con == NULL) {
            cout << "Error: " << mysql_error(con); // 输出错误信息
            exit(1); // 退出程序
        }

        // 更新连接池和空闲连接数量
        connList.push_back(con); // 将连接添加到连接池列表
        ++FreeConn; // 空闲连接数加一
    }

    // 信号量初始化为最大连接数
    reserve = sem(FreeConn);

    this->MaxConn = FreeConn; // 最大连接数等于空闲连接数
}

获取 && 释放连接

当线程数量大于数据库连接数量,使用信号量进行同步,每次取出连接,信号量原子 -1,释放连接原子 +1;若连接池内没有连接了,则阻塞等待

另外,由于多线程操作连接池,会造成竞争,这里用 互斥锁 完成同步,具体的同步机制均使用 lock.h 中封装好的类

// 当有请求时,从数据库连接池返回一个可用连接,
// 更新使用和空闲连接数
MYSQL *connection_pool::GetConnection()
{
    MYSQL *con = NULL; // MySQL 连接指针

    if (0 == connList.size()) // 如果连接池为空,返回空指针
        return NULL;

    // 取出连接,信号量原子 -1,为 0 则等待
    reserve.wait(); // 等待信号量

    lock.lock(); // 加锁

    con = connList.front(); // 获取连接池中的第一个连接
    connList.pop_front(); // 弹出连接

    // 这里两个变量,没有用到,鸡肋啊...
    --FreeConn; // 空闲连接数减一
    ++CurConn; // 当前连接数加一

    lock.unlock(); // 解锁
    return con; // 返回连接
}

// 释放当前使用的连接
bool connection_pool::ReleaseConnection(MYSQL *con)
{
    if (NULL == con) // 如果连接为空,返回false
        return false;

    lock.lock(); // 加锁

    connList.push_back(con); // 将连接放回连接池
    ++FreeConn; // 空闲连接数加一
    --CurConn; // 当前连接数减一

    lock.unlock(); // 解锁

    // 释放连接原子 +1
    reserve.post(); // 释放信号量
    return true; // 返回true
}

销毁连接池

1)通过 迭代器 遍历连接池链表

2)关闭对应数据库连接

3)清空链表

4)并重置空闲连接和现有连接数量

// 销毁数据库连接池
void connection_pool::DestroyPool()
{
    lock.lock(); // 加锁
    if (connList.size() > 0) { // 如果连接池不为空
        // 迭代器遍历,关闭数据库连接
        list<MYSQL *>::iterator it; // 声明迭代器it,用于遍历connList列表
        for (it = connList.begin(); it != connList.end(); ++it) // 遍历连接池中的每个连接
        {
            MYSQL *con = *it; // 获取迭代器指向的连接
            mysql_close(con); // 关闭数据库连接
        }
        CurConn = 0; // 将当前连接数设置为0
        FreeConn = 0; // 将空闲连接数设置为0

        // 清空list
        connList.clear(); // 清空连接池列表

        lock.unlock(); // 解锁
    }

    // 无论是否进入 if 分支,都能正确释放互斥锁
    lock.unlock(); // 解锁
}

🍑RAII 机制释放数据库连接

数据库连接的获取与释放通过 RAII 机制封装,避免手动释放

定义

需要注意的是,获取连接时,通过有参构造对传入的参数进行修改;

其中,数据库连接本身是指针类型,所以参数需要通过双指针才能对其进行修改

双指针:指向指针的指针,避免暴露指针内部细节 

class connectionRAII {
public:
    // 双指针对 MYSQL *con 修改
    connectionRAII(MYSQL **con, connection_pool *connPool); // 构造函数,传入双指针用于修改MYSQL *con
    ~connectionRAII(); // 析构函数

private:
    MYSQL *conRAII; // 数据库连接指针
    connection_pool *poolRAII; // 连接池指针
};

实现

 不直接调用  获取和释放连接的接口,将其封装起来,通过 RAII 机制进行获取和释放

connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool)
{
    // 通过双指针传入的MYSQL **SQL,将其指向连接池中获取的连接
    *SQL = connPool->GetConnection();

    // 将获取到的连接赋值给conRAII数据库连接指针
    conRAII = *SQL;
    // 将传入的连接池指针赋值给poolRAII
    poolRAII = connPool;
}

// 析构函数,用于释放连接
connectionRAII::~connectionRAII()
{
    // 通过连接池指针释放连接conRAII
    poolRAII->ReleaseConnection(conRAII);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千帐灯无此声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值