基于C++“简单且有效”的“数据库连接池”

打印 上一主题 下一主题

主题 837|帖子 837|积分 2521

前言


  • 数据库连接池在开发中应该是很常用的一个组件,他可以很好的节流连接数据库的时间开销;
  • 本文基使用C++实现了一个简单的数据库连接池,代码量只有400行只有,但是压力测试效果很好;
  • 欢迎收藏 + 关注,本人将会持续更新后端与AI算法有关知识点。

  
MySQL 数据库是基于 C/S 模式的,在每一次访问mysql服务器的时间,都必要创建一个TCP连接,但是,在高并发情况下,大量的 TCP 三次握手、MySQL Server 连接认证、MySQL Server 关闭连接接纳资源和 TCP 四次挥手所耗费的性能变乱也是很明显的,故设置连接池就是为了减少这一部分的性能损耗
连接池功能点介绍

初始连接量

表示连接池事先会和 MySQL Serve r创建最小个数的连接,当应用发起 MySQL 访问时,不用再创建和 MySQL Server 新的连接,直接从连接池中获取一个可用的连接即可,使用完成后并不去释放连接,而是把连接再归还到连接池当中
最大连接量

当并发访问MySQL Server的请求增多时,初始连接量已经不够用了,此时会去创建更多的连接给应用去使用,是新创建的连接数量上限是maxSize,不能无限定的创建连接。而且当这些连接使用完之后,再次归还到连接池当中来维护。
最大空闲时间

当访问MySQL的并发请求多了以后,连接池里面的连接数量会动态增加,上限是maxSize 个,当这些连接用完会再次归还到连接池当中。如果在指定的时间内这些新增的连接都没有被再次使用过,那么新增加的这些连接资源就要被接纳掉,只必要保持初始连接量个连接即可
连接超时时间

当MySQL的并发请求量过大,连接池中的连接数量已经到达最大数量了,而此时没有空闲的连接可供使用,那么此时应用从连接池获取连接无法乐成,它通过阻塞的方式获取连接的时间,如果超过一个时间,那么获取连接失败
连接池主要包罗了以下功能点



  • 单例模式设置连接池;
  • 向用户提供一个接口,可以从池中拿到一个数据库连接;
  • 采用生产者-消费者模式,池用队列作为缓冲区,实现生成连接和拿取连接;
  • 采用锁,在创建、拿取连接中进行加锁;
  • 采用智能指针管理从队列中获取的连接,而且采用lambda实现智能指针的析构函数,将连接从新放回队列中;
  • 设置生产连接、接纳连接线程,而且驻留背景,作为守护线程;
  • 采用原子变量才记录当前池中的连接数。
创建目录如下

代码

logger.h
  1. #ifndef PUBLIC_H_
  2. #define PUBLIC_H_
  3. #include <iostream>
  4. // 作用:封装简单的LOG
  5. #define LOGGER(str) std::cout << "====》" << __LINE__ << " time: " << __TIME__ << " message: " << str << std::endl;
  6. #endif // !PUBLIC_H_
复制代码
connection.h
  1. #ifndef CONNECTION_H_
  2. #define CONNECTION_H_
  3. #include <mysql/mysql.h>
  4. #include <ctime>
  5. #include <string>
  6. /*
  7. 功能:
  8.     初始化数据库连接
  9.     释放连接
  10.     连接数据库
  11.     查询mysql
  12.     修改数据库数据
  13.     刷新/设置空闲时间的起始时间点
  14.     返回空闲时间
  15. */
  16. class Connection
  17. {
  18. public:
  19.     // 初始化数据库连接
  20.     Connection();
  21.     // 释放连接
  22.     ~Connection();
  23.     // 连接数据库
  24.     bool connectionSqlBase(std::string ip, unsigned int port, std::string user, std::string passward, std::string dbName);
  25.     // 查询mysql
  26.     MYSQL_RES* query(std::string sql);
  27.     // 修改数据库数据
  28.     bool modify(std::string sql);
  29.     // 刷新/设置空闲时间的起始时间点
  30.     void setStartActivateTime();
  31.     // 返回空闲时间
  32.     clock_t getActivateTime();
  33. private:
  34.     MYSQL* m_sqlConn{};   // 连接mysql服务器
  35.     clock_t m_activateTime;  // 记录空闲时间的起始点
  36. };
  37. #endif // !CONNECTION_H_
复制代码
connection.cpp
  1. #include "connection.h"
  2. #include "logger.h"
  3. // 初始化数据库连接
  4. Connection::Connection()
  5. {
  6.     m_sqlConn = mysql_init(nullptr);
  7.     if(m_sqlConn == nullptr) {
  8.         LOGGER("mysql init false !!!");
  9.         return;
  10.     }
  11. }
  12. // 释放连接
  13. Connection::~Connection()
  14. {
  15.     mysql_close(m_sqlConn);
  16. }
  17. // 连接数据库
  18. bool Connection::connectionSqlBase(std::string ip, unsigned int port, std::string user, std::string passward, std::string dbName)
  19. {
  20.     if(nullptr == mysql_real_connect(m_sqlConn, ip.c_str(), user.c_str(), passward.c_str(), dbName.c_str(), port, NULL, 0)) {
  21.         LOGGER("mysql connects error!!");
  22.         return false;
  23.     }
  24.     return true;
  25. }
  26. // 查询mysql
  27. MYSQL_RES* Connection::query(std::string sql)
  28. {
  29.     int res = mysql_query(m_sqlConn, sql.c_str());
  30.     if(0 != res) {
  31.         LOGGER("sql query false!!!");
  32.         return nullptr;
  33.     }
  34.     return mysql_use_result(m_sqlConn);
  35. }
  36. // 修改数据库数据
  37. bool Connection::modify(std::string sql)
  38. {
  39.     int res = mysql_query(m_sqlConn, sql.c_str());
  40.     if(0 != res) {
  41.         LOGGER("sql update/insert/select false!!!");
  42.         return false;
  43.     }
  44.     return true;
  45. }
  46. // 刷新/设置空闲时间的起始时间点
  47. void Connection::setStartActivateTime()
  48. {
  49.     m_activateTime = clock();
  50. }
  51. // 返回空闲时间
  52. clock_t Connection::getActivateTime()
  53. {
  54.     return clock() - m_activateTime;
  55. }
复制代码
dbConnectionPool.h
  1. #ifndef DBCONNECTION_H_
  2. #define DBCONNECTION_H_
  3. #include "connection.h"
  4. #include <mysql/mysql.h>
  5. #include <queue>
  6. #include <string>
  7. #include <condition_variable>
  8. #include <atomic>
  9. #include <memory>
  10. // 核心:生产者、消费者模式
  11. class DbConnPool
  12. {
  13. public:
  14.     // 单例模式
  15.     static DbConnPool* getDbConnPool();
  16.     // 对外提供接口: 获取连接的数据库,通过智能指针回收
  17.     std::shared_ptr<Connection> getMysqlConn();
  18.    
  19.     // 测试
  20.     // void test()
  21.     // {
  22.     //     readConfigurationFile();
  23.     // }
  24. private:
  25.     // 单例模型:构造函数私有化, 目的:创建最小连接数量
  26.     DbConnPool();
  27.     DbConnPool(const DbConnPool&) = delete;
  28.     DbConnPool operator=(const DbConnPool&) = delete;
  29.    
  30.     // 读取配置文件
  31.     bool readConfigurationFile();
  32.     // 如果没有Mysql连接了,则产生新连接,这个线程驻留后台(像守护线程一样)
  33.     void produceNewConn();
  34.     // 如果队列线程 > initSize, 且空闲时间大于最大空闲时间,则回收
  35.     void recycleConn();
  36. private:
  37.     // MYSQL连接信息
  38.     std::string m_ip;
  39.     unsigned int m_port;
  40.     std::string m_username;
  41.     std::string m_password;
  42.     std::string m_dbname;
  43.     // 数据库连接池信息
  44.     int m_initSize;
  45.     int m_maxSize;
  46.     int m_maxFreeTime;
  47.     int m_maxConnTime;
  48.     // 生产者、消费者共享内存:获取连接
  49.     std::queue<Connection*> m_connQueue;
  50.     // 存储当前存储到队列中存储的数量
  51.     std::atomic_int m_conntionCnt{};
  52.     // 锁
  53.     std::mutex m_queueMuetx;
  54.     // 生产者、消费者:产生连接和取连接
  55.     std::condition_variable m_cv;  // 用于生产线程和消费线程之间的通信
  56. };
  57. #endif // !DBCONNECTION_H_
复制代码
dbConnectionPool.cpp
  1. #include "dbConnectionPool.h"
  2. #include "connection.h"
  3. #include "logger.h"
  4. #include <iostream>
  5. #include <cstdio>
  6. #include <cstring>
  7. #include <mutex>
  8. #include <thread>
  9. #include <functional>
  10. #include <chrono>
  11. // 创建连接:new,但是拿取连接后是交给shared_ptr
  12. // 单例模式
  13. DbConnPool* DbConnPool::getDbConnPool()
  14. {
  15.     // 最简单的方式:static
  16.     static DbConnPool pool;
  17.     return &pool;
  18. }
  19. // 对外提供接口: 获取连接的数据库,通过智能指针回收
  20. std::shared_ptr<Connection> DbConnPool::getMysqlConn()
  21. {
  22.     std::unique_lock<std::mutex> lock(m_queueMuetx);
  23.     // 判断队列是否为空
  24.     while(m_connQueue.empty()) {
  25.         // 队列为空,则等待 最大连接时间, 即这个时候客户端请求连接,但是池里没有连接了,则会等待,如果超过了最大时间,则:连接失败
  26.         if(std::cv_status::timeout == m_cv.wait_for(lock, std::chrono::seconds(m_maxConnTime))) {
  27.             // 再次判断是否为空
  28.             if(m_connQueue.empty()) {
  29.                 LOGGER("no conntion !!!!");
  30.                 return nullptr;
  31.             }
  32.         }
  33.     }
  34.     /*
  35.     从队列中获取一个连接,交给**智能指针管理**
  36.         注意:删除连接有回收线程监控,而这里获取的连接使用完后,需要还给**队列中**,所以**需要重写智能指针的回收函数**
  37.     */
  38.    std::shared_ptr<Connection> sp(m_connQueue.front(), [&](Connection* pconn){
  39.         // 注意,这里需要加锁
  40.         std::unique_lock<std::mutex> lock(m_queueMuetx);
  41.         pconn->setStartActivateTime();
  42.         m_connQueue.push(pconn);  // 入队
  43.         m_conntionCnt++;   // +1
  44.    });
  45.    // 弹出队列
  46.     m_connQueue.pop();
  47.     m_conntionCnt--;   // -1
  48.     return sp;
  49. }
  50. // 单例模型:构造函数私有化
  51. DbConnPool::DbConnPool()
  52. {
  53.     if(readConfigurationFile() == false) {
  54.         return;
  55.     }
  56.     std::unique_lock<std::mutex> lock(m_queueMuetx);
  57.     for(int i = 0; i < m_maxSize; i++) {
  58.         Connection* newConn = new Connection();
  59.         newConn->connectionSqlBase(m_ip, m_port, m_username, m_password, m_dbname);
  60.         newConn->setStartActivateTime();   // 设置 空闲时间 的起始点
  61.         m_connQueue.push(newConn);      // 入队
  62.         m_conntionCnt++;     // 存储到队列中数据+1
  63.     }
  64.     // 开启线程:检查是否需要需要**新创建连接**
  65.     std::thread produce(std::bind(&DbConnPool::produceNewConn, this));
  66.     produce.detach();   // 驻留后台
  67.     // 开启线程,检查是否需要**删除连接**
  68.     std::thread search(std::bind(&DbConnPool::recycleConn, this));
  69.     search.detach();    // 驻留后台
  70. }
  71. // 读取配置文件
  72. bool DbConnPool::readConfigurationFile()
  73. {
  74.     FILE* fp = fopen("./mysql.ini", "r");
  75.     if(fp == nullptr) {
  76.         LOGGER("mysql.ini open false!!");
  77.         return false;
  78.     }
  79.     char buff[BUFSIZ] = { 0 };
  80.     while(!feof(fp)) {
  81.         // clear
  82.         memset(buff, 0, sizeof(buff));
  83.         // 读取
  84.         fgets(buff, BUFSIZ, fp);
  85.         std::string str = buff;
  86.         // 判空
  87.         if(str.empty()) {
  88.             continue;
  89.         }
  90.         // 截断
  91.         int idx = str.find('=', 0);
  92.         if(idx == -1) {
  93.             continue;
  94.         }
  95.         int end = str.find('\n', idx);
  96.         std::string key = str.substr(0, idx);
  97.         std::string value = str.substr(idx + 1, end - idx - 1);
  98.         //std::cout << "key: " << key << " value: " << value << std::endl;
  99.         if(key == "ip") {
  100.             m_ip = value;
  101.         } else if(key == "port") {
  102.             m_port = atoi(value.c_str());
  103.         } else if(key == "username") {
  104.             m_username = value;
  105.         } else if(key == "password") {
  106.             m_password = value;
  107.         } else if(key == "dbname") {
  108.             m_dbname = value;
  109.         } else if(key == "initSize") {
  110.             m_initSize = atoi(value.c_str());
  111.         } else if(key == "maxSize") {
  112.             m_maxSize = atoi(value.c_str());
  113.         } else if(key == "maxFreeTime") {
  114.             m_maxFreeTime = atoi(value.c_str());
  115.         } else if(key == "maxConnTime") {
  116.             m_maxConnTime = atoi(value.c_str());
  117.         }
  118.     }
  119.     std::cout << m_ip << " " << m_port << " " << m_username << " " << m_password << " " << m_dbname << " " << m_initSize << " " << m_maxSize << " " << m_maxFreeTime << " " << m_maxConnTime << std::endl;
  120.     return true;
  121. }
  122. // 如果池里没有Mysql连接了,则产生新连接,这个线程驻留后台(像守护线程一样)
  123. /*
  124. 实现思路:
  125.     设置一个循环:循环检查
  126.         如果队列不为空,则条件变量一直等待
  127. */
  128. void DbConnPool::produceNewConn()
  129. {
  130.     for(;;) {
  131.         std::unique_lock<std::mutex> lock(m_queueMuetx);
  132.         while(!m_connQueue.empty()) {
  133.             m_cv.wait(lock);    // 条件变量一直等待
  134.         }
  135.         // 这个时候,队列为空,从新创建连接
  136.         for(int i = 0; i < m_maxSize; i++) {
  137.             Connection* newConn = new Connection();
  138.             newConn->setStartActivateTime();   // 刷新时间
  139.             m_connQueue.push(newConn);
  140.             m_conntionCnt++;   // +1
  141.         }
  142.         // 通知等待线程
  143.         m_cv.notify_all();
  144.     }
  145. }
  146. // 如果队列线程 > initSize, 且空闲时间大于最大空闲时间,则回收
  147. void DbConnPool::recycleConn()
  148. {
  149.     for(;;) {
  150.         std::unique_lock<std::mutex> lock(m_queueMuetx);
  151.         while(m_conntionCnt > m_initSize) {
  152.             Connection* conn = m_connQueue.front();
  153.             // 超过最大空闲时间
  154.             if((static_cast<double>(conn->getActivateTime()) / CLOCKS_PER_SEC) > m_maxFreeTime) {
  155.                 m_connQueue.pop();
  156.                 m_conntionCnt--;
  157.                 delete conn;
  158.             } else {   // 对头没超过,则直接退出
  159.                 break;
  160.             }
  161.         }
  162.     }
  163. }
复制代码
压力测试

分别插入10000条数据,对没有使用连接池和使用连接池分别进行测试。
  1. #include "dbConnectionPool.h"
  2. #include "connection.h"
  3. #include <iostream>
  4. #include <mysql/mysql.h>
  5. #include <chrono>
  6. int main() {
  7.     auto start = std::chrono::high_resolution_clock::now(); // 获取当前时间点
  8.     for(int i = 0; i < 10000; i++) {
  9. #if 1
  10.         DbConnPool* pool = DbConnPool::getDbConnPool();
  11.         std::shared_ptr<Connection> conn = pool->getMysqlConn();
  12.         char sql[1024] = { 0 };
  13.         sprintf(sql, "insert into temp(name, age, sex) values('%s', '%d', '%s');", "yxz", 18, "man");
  14.         conn->modify(sql);
  15. #elif  0
  16.         Connection conn;
  17.         conn.connectionSqlBase("127.0.0.1", 3306, "root", "wy2892586", "test");
  18.         char sql[1024] = { 0 };
  19.         sprintf(sql, "insert into temp(name, age, sex) values('%s', '%d', '%s');", "yxz", 18, "man");
  20.         conn.modify(sql);
  21. #endif
  22.     }
  23.     auto end = std::chrono::high_resolution_clock::now(); // 获取结束时间点
  24.     std::chrono::duration<double, std::milli> duration = end - start; // 计算持续时间,并转换为毫秒
  25.     std::cout << "Time: " << duration.count() << " ms" << std::endl;
  26.     return 0;
  27. }
复制代码
效果如下:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表