前言
- 数据库连接池在开发中应该是很常用的一个组件,他可以很好的节流连接数据库的时间开销;
- 本文基使用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
- #ifndef PUBLIC_H_
- #define PUBLIC_H_
- #include <iostream>
- // 作用:封装简单的LOG
- #define LOGGER(str) std::cout << "====》" << __LINE__ << " time: " << __TIME__ << " message: " << str << std::endl;
- #endif // !PUBLIC_H_
复制代码 connection.h
- #ifndef CONNECTION_H_
- #define CONNECTION_H_
- #include <mysql/mysql.h>
- #include <ctime>
- #include <string>
- /*
- 功能:
- 初始化数据库连接
- 释放连接
- 连接数据库
- 查询mysql
- 修改数据库数据
- 刷新/设置空闲时间的起始时间点
- 返回空闲时间
- */
- class Connection
- {
- public:
- // 初始化数据库连接
- Connection();
- // 释放连接
- ~Connection();
- // 连接数据库
- bool connectionSqlBase(std::string ip, unsigned int port, std::string user, std::string passward, std::string dbName);
- // 查询mysql
- MYSQL_RES* query(std::string sql);
- // 修改数据库数据
- bool modify(std::string sql);
- // 刷新/设置空闲时间的起始时间点
- void setStartActivateTime();
- // 返回空闲时间
- clock_t getActivateTime();
- private:
- MYSQL* m_sqlConn{}; // 连接mysql服务器
- clock_t m_activateTime; // 记录空闲时间的起始点
- };
- #endif // !CONNECTION_H_
复制代码 connection.cpp
- #include "connection.h"
- #include "logger.h"
- // 初始化数据库连接
- Connection::Connection()
- {
- m_sqlConn = mysql_init(nullptr);
- if(m_sqlConn == nullptr) {
- LOGGER("mysql init false !!!");
- return;
- }
- }
- // 释放连接
- Connection::~Connection()
- {
- mysql_close(m_sqlConn);
- }
- // 连接数据库
- bool Connection::connectionSqlBase(std::string ip, unsigned int port, std::string user, std::string passward, std::string dbName)
- {
- if(nullptr == mysql_real_connect(m_sqlConn, ip.c_str(), user.c_str(), passward.c_str(), dbName.c_str(), port, NULL, 0)) {
- LOGGER("mysql connects error!!");
- return false;
- }
- return true;
- }
- // 查询mysql
- MYSQL_RES* Connection::query(std::string sql)
- {
- int res = mysql_query(m_sqlConn, sql.c_str());
- if(0 != res) {
- LOGGER("sql query false!!!");
- return nullptr;
- }
- return mysql_use_result(m_sqlConn);
- }
- // 修改数据库数据
- bool Connection::modify(std::string sql)
- {
- int res = mysql_query(m_sqlConn, sql.c_str());
- if(0 != res) {
- LOGGER("sql update/insert/select false!!!");
- return false;
- }
- return true;
- }
- // 刷新/设置空闲时间的起始时间点
- void Connection::setStartActivateTime()
- {
- m_activateTime = clock();
- }
- // 返回空闲时间
- clock_t Connection::getActivateTime()
- {
- return clock() - m_activateTime;
- }
复制代码 dbConnectionPool.h
- #ifndef DBCONNECTION_H_
- #define DBCONNECTION_H_
- #include "connection.h"
- #include <mysql/mysql.h>
- #include <queue>
- #include <string>
- #include <condition_variable>
- #include <atomic>
- #include <memory>
- // 核心:生产者、消费者模式
- class DbConnPool
- {
- public:
- // 单例模式
- static DbConnPool* getDbConnPool();
- // 对外提供接口: 获取连接的数据库,通过智能指针回收
- std::shared_ptr<Connection> getMysqlConn();
-
- // 测试
- // void test()
- // {
- // readConfigurationFile();
- // }
- private:
- // 单例模型:构造函数私有化, 目的:创建最小连接数量
- DbConnPool();
- DbConnPool(const DbConnPool&) = delete;
- DbConnPool operator=(const DbConnPool&) = delete;
-
- // 读取配置文件
- bool readConfigurationFile();
- // 如果没有Mysql连接了,则产生新连接,这个线程驻留后台(像守护线程一样)
- void produceNewConn();
- // 如果队列线程 > initSize, 且空闲时间大于最大空闲时间,则回收
- void recycleConn();
- private:
- // MYSQL连接信息
- std::string m_ip;
- unsigned int m_port;
- std::string m_username;
- std::string m_password;
- std::string m_dbname;
- // 数据库连接池信息
- int m_initSize;
- int m_maxSize;
- int m_maxFreeTime;
- int m_maxConnTime;
- // 生产者、消费者共享内存:获取连接
- std::queue<Connection*> m_connQueue;
- // 存储当前存储到队列中存储的数量
- std::atomic_int m_conntionCnt{};
- // 锁
- std::mutex m_queueMuetx;
- // 生产者、消费者:产生连接和取连接
- std::condition_variable m_cv; // 用于生产线程和消费线程之间的通信
- };
- #endif // !DBCONNECTION_H_
复制代码 dbConnectionPool.cpp
- #include "dbConnectionPool.h"
- #include "connection.h"
- #include "logger.h"
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <mutex>
- #include <thread>
- #include <functional>
- #include <chrono>
- // 创建连接:new,但是拿取连接后是交给shared_ptr
- // 单例模式
- DbConnPool* DbConnPool::getDbConnPool()
- {
- // 最简单的方式:static
- static DbConnPool pool;
- return &pool;
- }
- // 对外提供接口: 获取连接的数据库,通过智能指针回收
- std::shared_ptr<Connection> DbConnPool::getMysqlConn()
- {
- std::unique_lock<std::mutex> lock(m_queueMuetx);
- // 判断队列是否为空
- while(m_connQueue.empty()) {
- // 队列为空,则等待 最大连接时间, 即这个时候客户端请求连接,但是池里没有连接了,则会等待,如果超过了最大时间,则:连接失败
- if(std::cv_status::timeout == m_cv.wait_for(lock, std::chrono::seconds(m_maxConnTime))) {
- // 再次判断是否为空
- if(m_connQueue.empty()) {
- LOGGER("no conntion !!!!");
- return nullptr;
- }
- }
- }
- /*
- 从队列中获取一个连接,交给**智能指针管理**
- 注意:删除连接有回收线程监控,而这里获取的连接使用完后,需要还给**队列中**,所以**需要重写智能指针的回收函数**
- */
- std::shared_ptr<Connection> sp(m_connQueue.front(), [&](Connection* pconn){
- // 注意,这里需要加锁
- std::unique_lock<std::mutex> lock(m_queueMuetx);
- pconn->setStartActivateTime();
- m_connQueue.push(pconn); // 入队
- m_conntionCnt++; // +1
- });
- // 弹出队列
- m_connQueue.pop();
- m_conntionCnt--; // -1
- return sp;
- }
- // 单例模型:构造函数私有化
- DbConnPool::DbConnPool()
- {
- if(readConfigurationFile() == false) {
- return;
- }
- std::unique_lock<std::mutex> lock(m_queueMuetx);
- for(int i = 0; i < m_maxSize; i++) {
- Connection* newConn = new Connection();
- newConn->connectionSqlBase(m_ip, m_port, m_username, m_password, m_dbname);
- newConn->setStartActivateTime(); // 设置 空闲时间 的起始点
- m_connQueue.push(newConn); // 入队
- m_conntionCnt++; // 存储到队列中数据+1
- }
- // 开启线程:检查是否需要需要**新创建连接**
- std::thread produce(std::bind(&DbConnPool::produceNewConn, this));
- produce.detach(); // 驻留后台
- // 开启线程,检查是否需要**删除连接**
- std::thread search(std::bind(&DbConnPool::recycleConn, this));
- search.detach(); // 驻留后台
- }
- // 读取配置文件
- bool DbConnPool::readConfigurationFile()
- {
- FILE* fp = fopen("./mysql.ini", "r");
- if(fp == nullptr) {
- LOGGER("mysql.ini open false!!");
- return false;
- }
- char buff[BUFSIZ] = { 0 };
- while(!feof(fp)) {
- // clear
- memset(buff, 0, sizeof(buff));
- // 读取
- fgets(buff, BUFSIZ, fp);
- std::string str = buff;
- // 判空
- if(str.empty()) {
- continue;
- }
- // 截断
- int idx = str.find('=', 0);
- if(idx == -1) {
- continue;
- }
- int end = str.find('\n', idx);
- std::string key = str.substr(0, idx);
- std::string value = str.substr(idx + 1, end - idx - 1);
- //std::cout << "key: " << key << " value: " << value << std::endl;
- if(key == "ip") {
- m_ip = value;
- } else if(key == "port") {
- m_port = atoi(value.c_str());
- } else if(key == "username") {
- m_username = value;
- } else if(key == "password") {
- m_password = value;
- } else if(key == "dbname") {
- m_dbname = value;
- } else if(key == "initSize") {
- m_initSize = atoi(value.c_str());
- } else if(key == "maxSize") {
- m_maxSize = atoi(value.c_str());
- } else if(key == "maxFreeTime") {
- m_maxFreeTime = atoi(value.c_str());
- } else if(key == "maxConnTime") {
- m_maxConnTime = atoi(value.c_str());
- }
- }
- std::cout << m_ip << " " << m_port << " " << m_username << " " << m_password << " " << m_dbname << " " << m_initSize << " " << m_maxSize << " " << m_maxFreeTime << " " << m_maxConnTime << std::endl;
- return true;
- }
- // 如果池里没有Mysql连接了,则产生新连接,这个线程驻留后台(像守护线程一样)
- /*
- 实现思路:
- 设置一个循环:循环检查
- 如果队列不为空,则条件变量一直等待
- */
- void DbConnPool::produceNewConn()
- {
- for(;;) {
- std::unique_lock<std::mutex> lock(m_queueMuetx);
- while(!m_connQueue.empty()) {
- m_cv.wait(lock); // 条件变量一直等待
- }
- // 这个时候,队列为空,从新创建连接
- for(int i = 0; i < m_maxSize; i++) {
- Connection* newConn = new Connection();
- newConn->setStartActivateTime(); // 刷新时间
- m_connQueue.push(newConn);
- m_conntionCnt++; // +1
- }
- // 通知等待线程
- m_cv.notify_all();
- }
- }
- // 如果队列线程 > initSize, 且空闲时间大于最大空闲时间,则回收
- void DbConnPool::recycleConn()
- {
- for(;;) {
- std::unique_lock<std::mutex> lock(m_queueMuetx);
- while(m_conntionCnt > m_initSize) {
- Connection* conn = m_connQueue.front();
- // 超过最大空闲时间
- if((static_cast<double>(conn->getActivateTime()) / CLOCKS_PER_SEC) > m_maxFreeTime) {
- m_connQueue.pop();
- m_conntionCnt--;
- delete conn;
- } else { // 对头没超过,则直接退出
- break;
- }
- }
- }
- }
复制代码 压力测试
分别插入10000条数据,对没有使用连接池和使用连接池分别进行测试。
- #include "dbConnectionPool.h"
- #include "connection.h"
- #include <iostream>
- #include <mysql/mysql.h>
- #include <chrono>
- int main() {
- auto start = std::chrono::high_resolution_clock::now(); // 获取当前时间点
- for(int i = 0; i < 10000; i++) {
- #if 1
- DbConnPool* pool = DbConnPool::getDbConnPool();
- std::shared_ptr<Connection> conn = pool->getMysqlConn();
- char sql[1024] = { 0 };
- sprintf(sql, "insert into temp(name, age, sex) values('%s', '%d', '%s');", "yxz", 18, "man");
- conn->modify(sql);
- #elif 0
- Connection conn;
- conn.connectionSqlBase("127.0.0.1", 3306, "root", "wy2892586", "test");
- char sql[1024] = { 0 };
- sprintf(sql, "insert into temp(name, age, sex) values('%s', '%d', '%s');", "yxz", 18, "man");
- conn.modify(sql);
- #endif
- }
- auto end = std::chrono::high_resolution_clock::now(); // 获取结束时间点
- std::chrono::duration<double, std::milli> duration = end - start; // 计算持续时间,并转换为毫秒
- std::cout << "Time: " << duration.count() << " ms" << std::endl;
- return 0;
- }
复制代码 效果如下:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |