news 2026/8/7 7:28:44

数据库连接池

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据库连接池

引言

本篇文章介绍一个四大池中的数据库连接池,里面会涉及到信号队列,智能指针,对数据库操作的二次封装(c接口)等一些基本的操作,实现连接池的四个必备的组成部分:初始连接量,最大连接量,最大空闲时间,和超时连接时间。

简介

我们为什么要数据库连接池,解决的问题主要是高并发的场景。当同一时间大量的连接请求访问数据库的时候,如果没有数据库连接池,那么我们的数据库就会创建上万个socket来和客户端进行连接,这对于性能的消耗是非常大的。而且对于TCP来说,我们需要有三次握手和四次挥手,这种耗时的操作也会导致网络的卡顿,每当数据的发出我们都需要三次握手和四次挥手。所以我们参考线程池的特点,我们可以创建一个队列,来存储一定量的connection,当有客户端访问的时候,我们的connection可以直接分配,这样子我们我们只需要建立依次连接,拿到对应的connection,之后发送的信息全部都可以通过已有的连接发送信息。

当客户量极大幅度增加的时候,我们就需要扩容我们的连接池,但是扩容是有一个限度的,我们记作maxConnectionSize,但是当用户量变少之后,我们不再需要这么多connection,我们就要减少这些数量,恢复到刚开始,那这个恢复的条件就是一个计时的过程,我们这里可以设置成100ms。

当客户量下降了之后,我们就不再需要这么多的连接量了,所以这个时候我们通过计时来判断什么时候可以把connection的数量恢复到最初值。

最后我们的连接断开之后我们需要回到队列里面,所以我们需要重写智能指针的析构函数。

代码

首先我们写一个处理数据库c接口的一个模板,除此之外,对于每一个连接,我们都需要设置几个函数来处理如果一段时间之后连接池connection数量恢复到初始化,一个是得到连接的起始值,一个是记录这个连接最新存在的时间。

#pragma once #ifndef _CONNECTION_H_ #define _CONNECTION_H_ #include <mysql.h> #include <string> #include <chrono> namespace Litguidyo { class Connection { public: Connection(); ~Connection(); bool connect(std::string ip, unsigned short port, std::string user, std::string password, std::string dbname); bool update(std::string sql); MYSQL_RES* query(std::string sql); void refreshAliveTime() { _aliveTime = std::chrono::steady_clock::now(); } std::chrono::milliseconds getAliveTime(); private: MYSQL* _connection; // MySQL和Server的一个链接 std::chrono::steady_clock::time_point _aliveTime; // 记录进入空闲状态的存活时间 _ }; } #endif
#include "Connection.h" #include "../Util/Helper.h" Litguidyo::Connection::Connection() { _connection = mysql_init(nullptr); } Litguidyo::Connection::~Connection() { if (_connection != nullptr) { mysql_close(_connection); } } bool Litguidyo::Connection::connect(std::string ip, unsigned short port, std::string user, std::string password, std::string dbname) { MYSQL* p = mysql_real_connect(_connection, ip.c_str(), user.c_str(), password.c_str(), dbname.c_str(), port, nullptr, 0); return p != nullptr; } bool Litguidyo::Connection::update(std::string sql) { if (mysql_query(_connection, sql.c_str())) { // LOG("update failed" + sql); return false; } return true; } MYSQL_RES* Litguidyo::Connection::query(std::string sql) { if (mysql_query(_connection, sql.c_str())) { LOG("query failed" + sql); return nullptr; } return mysql_use_result(_connection); } std::chrono::milliseconds Litguidyo::Connection::getAliveTime() { return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - _aliveTime); }

我们这里使用单例模式(懒汉模式),然后连接池里面记录了数据库的所有基本的信息,方便客户端的连接。

我们扩展连接数量的方式采用的是生产者-消费者模型,引入的是消息队列

#pragma once #include <queue> #include "../Connection/Connection.h" #include <vcruntime.h> #include <mutex> #include <atomic> #include <thread> #include <condition_variable> #include <memory> #ifndef _CONNECTIONPOOL_H_ #define _CONNECTIONPOOL_H_ namespace Litguidyo { class ConnectionPool { private: ConnectionPool(); ConnectionPool(std::string& path); ConnectionPool(const ConnectionPool&) = delete; ConnectionPool& operator=(const ConnectionPool&) = delete; public: static ConnectionPool* getInstance(); std::shared_ptr<Connection> getConnection(); private: std::string _ip; std::string _username; std::string _password; unsigned short _port; // 3306 size_t _initConnectionSize; size_t _maxConnectionSize; size_t _maxIdleTime; // 连接池最大空闲时间 size_t _connectionTimeout; // 连接池最大超时时间 std::string _dbname; // 数据库名 std::queue<Connection*> _connectionQueue; std::mutex _queueMutex; // 维护队列线程安全 std::atomic<size_t> _currentConnectionSize; // 当前连接池中连接数 bool loadConfigPool(); void produceConnectionTask(); // 运行在独立线程中,专门负责生成新连接 std::condition_variable cv; // 设置条件变量,用于连接生产线程和消费线程的通信 void scannerConnectionTask(); // 运行在独立线程中,专门负责扫描超过maxIdleTime时间的空闲连接,进行对于连接的回收 }; } #endif

初始化先创建少量的连接数量,同时构建两个线程,一个线程专门创建连接为了应对connection不够的情况,一个线程专门扫描队列,把那些超时的连接全部断开然后重新放入队列里面。

Litguidyo::ConnectionPool::ConnectionPool() { if (!loadConfigPool()) { return; } for (size_t i = 0; i < _initConnectionSize; i++) { Connection* connection = new Connection(); connection->connect(_ip, _port, _username, _password, _dbname); connection->refreshAliveTime(); // 刷新起始时间 _connectionQueue.push(connection); _currentConnectionSize++; } std::thread produce(std::bind(&ConnectionPool::produceConnectionTask, this)); produce.detach(); // 启动一个新的线程,扫描超过,maxIdleTime时间的空闲连接,进行对于连接的回收 std::thread scanner(std::bind(&ConnectionPool::scannerConnectionTask, this)); scanner.detach(); }

按照代码的思路,我们取到connection之后什么时候释放应该是由客户端来决定,所以选择的是智能指针,但是当释放了之后,并不是delete,而是重新放回队列里面,所以要重写析构函数。

我们取connection的时候,有可能里面已经没有connection了,所以我们需要判断一下,不过不是没有就立马返回客户端,而是等待100ms(持有锁),这个中间如果有connection被释放了,那么就会立刻跳过if,执行while()循环,然后跳出。

std::shared_ptr<Litguidyo::Connection> Litguidyo::ConnectionPool::getConnection() { std::unique_lock<std::mutex> lock(_queueMutex); while (_connectionQueue.empty()) { // 让线程先等待100ms,如果100ms内没有连接,就继续执行代码 if (std::cv_status::timeout == cv.wait_for(lock, std::chrono::microseconds(_connectionTimeout))) { if (_connectionQueue.empty()) { LOG("ConnectionPool is empty, please check the configuration file! "); return nullptr; } } } std::shared_ptr<Connection> sp(_connectionQueue.front(), [&](Connection* p) { std::lock_guard<std::mutex> lock(_queueMutex); p->refreshAliveTime(); _connectionQueue.push(p); }); _connectionQueue.pop(); if (_connectionQueue.empty()) { cv.notify_all(); // 通知消费线程 } return sp; }

只有队列是空的才会生产connection(不够用了),并且还不可以超过最大的值,当生产完了之火唤醒消费者。因为这里只有一个生产线程,所以完全不需要担心通知到生产线程,并且有while()来保证安全。

void Litguidyo::ConnectionPool::produceConnectionTask() { while(true) { std::unique_lock<std::mutex> lock(_queueMutex); while (!_connectionQueue.empty()) { cv.wait(lock); // 生产线程进行等待 } if (_currentConnectionSize < _maxConnectionSize) { Connection* connection = new Connection(); connection->refreshAliveTime(); // 刷新起始时间 connection->connect(_ip, _port, _username, _password, _dbname); _connectionQueue.push(connection); _currentConnectionSize++; } cv.notify_all(); // 通知消费线程 } }

这个线程不是一直循环,而是隔一段时间扫描一次,如果发现队列里面的数量已经超过了初始化的数量,而且存活时间已经超过了限制(这个connection没有被长时间使用),就会删除。

void Litguidyo::ConnectionPool::scannerConnectionTask() { while (true) { std::this_thread::sleep_for(std::chrono::milliseconds(_maxIdleTime)); std::unique_lock<std::mutex> lock(_queueMutex); while (_currentConnectionSize > _initConnectionSize) { Connection* temp = _connectionQueue.front(); if (temp->getAliveTime() > std::chrono::milliseconds(_maxIdleTime)) { _connectionQueue.pop(); _currentConnectionSize--; delete temp; } else { break; // 队头都没有超时,那么之后的连接都不可能超时,不需要继续扫描 } } } }

总结

本篇文章到这里就结束了!!!希望可以帮助大家理解~~~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/7 7:28:38

自动化运维-从零开始编写ansible剧本(一)

从零开始编写Asible剧本 在日常运维中&#xff0c;我们经常需要在大量服务器上执行重复性任务——比如安装软件、修改配置、重启服务。Ansible 提供了两种执行方式&#xff1a;Ad-Hoc 命令 和 Playbook&#xff08;剧本&#xff09;。Ad-Hoc 适合临时性的快速操作&#xff0c;…

作者头像 李华
网站建设 2026/8/7 7:28:06

唐诗宋词知识图谱构建完整实施方案

一、项目概述1.1 项目目标要构建出结构化、同时关联化、并且可推理的唐诗宋词领域知识图谱, 还要打通诗人、以及词作、再加上朝代、还有意象、包括典故、涉及地理、有关格律、关乎情感这八大核心维度的关联关系, 最终实现将古典诗词知识予以系统化进行梳理、能够智能检索、可以…

作者头像 李华
网站建设 2026/8/7 7:27:56

SpringBoot AOP统一Web请求日志:从原理到生产级实现

1. 项目缘起&#xff1a;为什么我们需要统一处理Web请求日志&#xff1f;在任何一个后端服务里&#xff0c;日志都是我们排查问题的“眼睛”。尤其是Web请求日志&#xff0c;它记录了谁、在什么时候、用什么方式、访问了哪个接口、得到了什么结果。当线上出现一个诡异的接口超时…

作者头像 李华
网站建设 2026/8/7 7:25:20

Python实现标签算法求解ESPPRC:运筹优化与动态规划实践

1. 项目概述&#xff1a;当运筹学遇上Python&#xff0c;用标签算法破解ESPPRC难题如果你在物流、交通或资源调度领域摸爬滚打过&#xff0c;一定对“车辆路径问题”不陌生。简单说&#xff0c;就是怎么安排一队车&#xff0c;在满足一堆限制条件&#xff08;比如载重、时间窗、…

作者头像 李华
网站建设 2026/8/7 7:19:31

ARP欺骗与DNS劫持:从协议原理到实战攻防的局域网安全深度解析

1. 从一次“诡异”的断网说起&#xff1a;为什么你的网络总是不对劲&#xff1f;你有没有遇到过这种情况&#xff1f;办公室的Wi-Fi明明信号满格&#xff0c;但网页就是打不开&#xff0c;或者打开的是个山寨页面&#xff1b;家里的网络突然变得奇慢无比&#xff0c;重启路由器…

作者头像 李华
网站建设 2026/8/7 7:17:54

Replit设计马拉松:5万美元奖金,聚焦云端IDE的UI/UX创新

1. 先搞清楚这个设计马拉松到底在做什么 如果你在开发者社区里看到“Replit 设计马拉松 5 万美元奖金”这个标题&#xff0c;第一反应可能是“又一个编程比赛”。但这次的重点不是写代码&#xff0c;而是 设计 。它解决的核心问题是&#xff1a;如何让一个功能强大的在线开发…

作者头像 李华