数据库连接池

前言

后端与数据库连接的常规流程:
image

数据库连接池是维持和管理一定数量连接的池式结构;它实现了1、复用连接:避免重复建立连接的开销;2、控制并发:限制最大连接数,防止数据库过载;3、提高响应速度:连接立即可用,减少等待时间
实现方式和种类:同步连接和异步连接。

同步连接:统数据库连接(如 JDBC Connection、在服务器启动时数据初始化)的典型模式。
通过接口返回值接受数据库返回值;用户请求直接获取数据库应答。描述着当前最多允许几个线程使用连接。
服务器发起请求,尝试获取连接池中的锁,基于这个连接访问了mysql,一连接一锁。获取连接时:如果连接池中没有空闲连接,调用线程会阻塞等待,直到其他线程归还连接或超时。执行SQL时:发送查询后线程阻塞,等待数据库返回结果后才继续执行后续代码。得到返回值后,解锁,获取句柄数据。
image

异步连接: 基于非阻塞 I/O 的数据库连接(如 R2DBC Connection中的连接、服务器的业务处理)。通过回调函数接受数据库返回值;用户请求间接(通过异步回调接收数据库返回)获取数据库应答。描述着当前最多允许几个连接同时执行 SQL 语句。
服务器发起请求,请求任务丢到任务队列,子线程从任务队列中取出任务,执行它的回调函数。获取连接时:连接池立即返回一个Future/Promise 或通过回调通知,调用线程不阻塞,子线程阻塞,可以继续处理其他任务。执行SQL时:发起查询后立即返回一个异步句柄,线程不等待结果;数据到达时通过回调、协程或响应式流处理。
image

mysql c/c++驱动(接口库)

  • libmysqlclient (c) 性能高 往往通过这个进行封装,
  • libmysqlcppconn c++实现,使用异常机制
  • 上述驱动都是阻塞io的实现

mysql c/c++驱动需要实现那些
mysql是基于TCP的, 字节流需要自定义包结构,(使用头部确定数据大小,使用指定字节作为数据尾部) 必须要实现mysql协议
io流程 注意这里是阻塞io
1、connect;
2、read、recv
3、write、send
4、close

实现(异步连接池)
异步线程池基于线程池原理实现了连接池,每一个线程绑定了一个连接,生产者将SQL请求包装成任务,向任务队列中PUSH请求,消费者线程取出一个任务,基于当前连接访问数据库。 (如何拿到结果)线程池在插入任务时,构建promise,并会返回future对象给生产者,promise传递到消费者线程,执行任务对promise,set_value,对应的future可以拿到数据库返回结果。 细节: 任务的包装(sql task promise) 返回(callback future)
基于连接区执行命令,等待结果的过程是一个耗时操作;
考虑使用线程池实现

耗时操作需要占用一个线程 一条连接需要占用一个线程
返回结果在生产者线程执行 future / promise 机制(由于收到结果的是连接池的线程,但是实际是生产者线程需要,因此发出了请求,所以考虑将结果在两个线程间传递。

理解几个对象:

#pragma once   //独立执行线程

#include <thread>
#include <atomic>
#include <memory>

class MySQLConn;  // 依赖一个MySQLConn对象 前置声明,而不是#include "MySQLConn.h"  // 直接包含头文件,避免循环依赖

template <typename T>
class BlockingQueue;

class SQLOperation;  //封装一个请求,表示线程即将进行的操作
class MySQLWorker {
public:
    MySQLWorker(MySQLConn *conn, BlockingQueue<std::unique_ptr<SQLOperation>> &task_queue, int heartbeat_interval_ms);
    ~MySQLWorker();

    void Start();
    void Stop();
    void RequestStop();
private:
    void Worker();
    MySQLConn *conn_;
    std::thread worker_;  // 独立执行线程
    BlockingQueue<std::unique_ptr<SQLOperation>> &task_queue_;
    int heartbeat_interval_ms_;
    std::atomic<bool> should_stop_{false};
};

worker表征一个线程, 从事件队列取出operation,并执行事件(operation),该operation基于连接conn_完成,返回的结果在operation中。

operation对象
补充一个:promise//future 一中类似于条件变量c++11的通信方式; promise可以向future或者shared_future单向发送一次信息(数据或者异常),future或者shared_future接受一次信息; promise的主模板 用于两个线程的按照值进行传递;引用模板 按照引用传递; void版本只发送信号,没有值;
image
1/共享状态对象(由指针管理)会在promise中创建。 用于存放设置的值或异常 2/移动构造函数;3/使用promise中de 共享状态对象 构造future对象;注意:promise和future是一一对应的 4/ 获取共享状态对象的值 ;5/ 设置共享状态对象的异常
image
1/获取共享状态对象的结果,如果对应状态没有就绪,处于阻塞;等待其就绪。2/等待就绪;3/等待指定时长,返回状态码ready就绪\timeout未就绪\deferred deferred共享状态含有延缓执行函数(显示请求才会执行(async中));4/等待到指定时间,返回状态码; 5/多个线程等待并读取同一个异步结果,实现future的复用
条件变量与promise/future的区别
image

言归正传,线程池每次请求,构建共享对象auto &future = promise.get_future(),自己持有future, 将promise层层传递到任务线程中,任务线程完成任务后,获得返回值,并设置promise.set_value(res); 线程池等待后获得future.get(),拿到数据库返回值;

关键

                      | 任务队列 task_queue_ | 回调队列 pending_queries_    |
                      | ---------------- | ------------------------ |
| 存放内容             | SQLOperation(待执行的SQL |QueryCallback(已提交、等结果的回调)
| 生产者              | 主线程 Query()            |主线程 AddQueryCallback()
| 消费者              | Worker 线程 PopWithTimeout|主线程轮询 InvokeIfReady
| 出队时机             | Worker 取出即出队         |回调执行完才出队(erase
| 同步机制             | 条件变量 not_empty_       |非阻塞轮询 wait_for(0s)

任务队列task_queue_,每一个工作线程在池的调度下,即池放出了接口Query,向任务队列中加任务SQLOperation指针,工作线程可以执行这个SQLOperation;任务的到来触发队列条件变量,使得那些在PopWithTimeout等待的线程继续执行,将SQLOperation带出去,并进行op->Execute(conn_);。多提一句,由于wait_for(lock, std::chrono::milliseconds(timeout_ms) 所以这个阻塞是存在超时的,如果超时,直接跳过,Worker查询连接心跳,再次循环等待。
有生产端使用的回调队列,对于每一个pool-》query的请求,对应回调QueryCallback,表示了请求结果和对应结果的处理函数。其中future和请求中的promise构成一对,待到线程执行完操作,promise.set——value,就获得触发future的值就有意义了。因此,我们使用pending_queries_数组把每个请求对应的回调管理起来。主函数轮询这个管理器,看哪个future就绪了,那提取内容,执行回调,并将其踢出数组。

posted @ 2026-08-04 11:33  超级麋鹿  阅读(10)  评论(0)    收藏  举报