文章目录
- 前置知识
- 关键技术点
- 项目背景
- 连接池功能点介绍
- MySQL Server参数介绍
- 功能设计
- 连接池功能点介绍
- 开发平台选型
- 关于MySQL数据库编程
- MySQL接口介绍
- 测试表设计
- Connection设计
- 数据库配置文件mysql.conf
- 日志文件log.hpp
- ConnectionPool设计
- 压力测试
- 源码链接:
前置知识
关键技术点
MySQL数据库编程、单例模式、queue队列容器、C++11多线程编程、线程互斥、线程同步通信和unique_lock
、基于CAS的原子整形、智能指针shared_ptr、lambda表达式、生产者-消费者线程模型
项目背景
MySQL是一个基于C/S设计的关系型数据库管理系统,一条SQL的执行需要通过mysql client发起一个连接,经过TCP三次握手完成TCP连接,然后再对客户端进行身份验证,验证成功后再把SQL发给mysql server(RDBMS)执行SQL(一般会涉及磁盘IO),然后给客户端返回执行结果,执行结束后,进行四次挥手断开连接
如果想要提高MySQL数据库(基于C/S设计)的访问瓶颈:
- 在服务器端增加缓存服务器,用户缓存常用的数据(例如redis),减少磁盘IO的次数
- 还可以使用连接池,来提高MySQL Server的访问效率
在高并发情况下:大量的TCP三次握手建立MySQL Server连接认证 以及 TCP四次挥手 MySQL Server关闭连接回收资源所耗费的性能时间也是很明显的,使用连接池就是为了减少这一部分的性能损耗
连接池功能点介绍
该项目是基于C++语言实现的连接池,主要也是实现几个所有连接池都支持的通用基础功能,连接池一般包含了以下内容:
- 数据库连接所用的ip地址
- port端口号
- 用户名和密码,连接哪个库
- 以及连接池的性能参数,例如 初始连接量,最大连接量,最大空闲时间、连接超时时间等,
1.初始连接量( i n i t S i z e initSize initSize):表示连接池会预先和MySQL Server创建 i n i t S i z e initSize initSize个连接,当用户发起MySQL访问时,不需要创建新的连接,直接从连接池中获取一个可用的连接就可以,使用完成后,并不去释放该连接,而是把当前连接重新归还到连接池当中
2.最大连接量( m a x S i z e maxSize maxSize):当并发访问MySQL Server的请求增多时,初始连接量已经不够使用的时候,此时会根据新的请求数量去创建更多的连接给用户去使用,但是新创建的连接数量上限是 m a x S i z e maxSize maxSize,不能无限制的创建连接。
- 因为每一个连接都会占用一个 s o c k e t socket socket资源,一般连接池和服务器程序是部署在一台主机上的,如果连接池占用过多的 s o c k e t socket socket资源,那么服务器就不能接收太多的客户端请求了。当这些连接使用完成后,再次归还到连接池当中来维护
3.最大空闲时间( m a x I d l e T i m e maxIdleTime maxIdleTime):当访问MySQL Server的并发请求多了以后,连接池里面的连接数量会动态增加,上限是 m a x S i z e maxSize maxSize个,当这些连接用完再次归还到连接池当中。如果在指定的 m a x I d l e T i m e maxIdleTime maxIdleTime里面,这些新增加的连接都没有被再次使用过,那么新增加的这些连接资源就要被回收掉,只需要保持初始连接量 i n i t S i z e initSize initSize个连接就可以了
4.连接超时时间( c o n n e c t i o n T i m e o u t connectionTimeout connectionTimeout):当MySQL Server的并发请求量过大,连接池中的连接数量已经到达 m a x S i z e maxSize maxSize了,而此时没有空闲的连接可供使用,那么此时用户无法从连接池获取连接,它通过”阻塞”的方式获取连接的时间如果超过 c o n n e c t i o n T i m e o u t connectionTimeout connectionTimeout,那么获取连接失败,无法访问数据库
MySQL Server参数介绍
show variables like 'max_connections';#查看MySQL Server所支持的最大连接个数
超过 m a x _ c o n n e c t i o n s max\_connections max_connections数量的连接,MySQL Server会直接拒绝访问,所以在使用连接池增加连接数量的时候,MySQL Server的 m a x c o n n e c t i o n s max_connections maxconnections参数也要适当的进行调整,以适配连接池的连接上限
功能设计
文件 | 功能 |
---|---|
$ConnectionPool.cpp $ 和 C o n n e c t i o n P o o l . h ConnectionPool.h ConnectionPool.h | 实现连接池 |
C o n n e c t i o n . c p p Connection.cpp Connection.cpp 和 C o n n e c t i o n . h Connection.h Connection.h | 描述每一条建立的连接:数据库操作代码、增删改查代码实现 |
连接池功能点介绍
1.连接池只需要一个实例,所以采用单例模式进行设计
2.服务器一般是多线程,可能多个线程都发起了对这个Mysql Server的操作请求,都需要从队列当中获取连接空闲连接,而 C o n n e c t i o n Connection Connection全部维护在一个连接队列中 => 所以需要保证连接队列的线程安全,需要使用互斥锁保证队列的线程安全
3.如果连接队列为空,还需要再获取连接,此时需要动态创建连接,上限数量是 m a x S i z e maxSize maxSize
4.连接队列中空闲连接时间超过 m a x I d l e T i m e maxIdleTime maxIdleTime的就要被释放掉,只保留初始的 i n i t S i z e initSize initSize个连接就可以了,这个功能点肯定需要放在独立的线程中去做
5.如果连接队列为空,而此时连接的数量已达上限 m a x S i z e maxSize maxSize,客户**“阻塞”**等待 c o n n e c t i o n T i m e o u t connectionTimeout connectionTimeout时间之后还没有获取到空闲的连接,那么获取连接失败
- 此处”阻塞”从Connection队列获取空闲连接,可以使用带超时时间的mutex互斥锁来实现连接超时时间
- 假设连接超时时间为100ms => 并不是直接睡眠100ms,而是在这100ms时间内,不断判断是否连接队列当中是否有空闲连接,如果有,那么直接获取队头的连接,否则连接池队列为空,就获取失败
6.用户获取的连接需要用shared_ptr
智能指针来管理,需要定制删除器 定制 连接释放的功能(此处并不是真正释放连接,而是把连接归还到连接池中)
7.新连接的生产线程 和 获取连接的线程 采用生产者-消费者线程模型来设计,所以需要使用线程间的同步通信机制来保证 => 条件变量和互斥锁
具体流程
假设我们的服务器给客户提供服务,客户端发起请求需要数据库操作时,Server需要到连接池管理的队列中获取一个连接,然后连接池给Server返回一个智能指针维护的连接,Server只管使用这条连接,无需关心这条连接的释放,然后使用这条连接去访问MySQL Server
开发平台选型
有关MySQL数据库编程、多线程编程、线程互斥和同步通信操作、智能指针、设计模式、容器等等这些技术在C++语言层面都可以直接实现,因此该项目选择直接在windows平台上进行开发
当然,因为采用的都是语言级别的接口,没有强依赖系统的接口,所以跨平台性比较好:放在Linux平台下用g++也可以直接编译运行,但是需要解决一些库的依赖问题
关于MySQL数据库编程
MySQL的windows安装文件云盘地址如下(下载development开发版:包含mysql头文件和libmysql库文件):链接:https://pan.baidu.com/s/1Y1l7qvpdR2clW5OCdOTwrQ提取码:95de
MySQL数据库编程直接采用oracle公司提供的MySQL C/C++客户端开发包,在VS上需要进行相应的头文件和库文件的配置,如下
- 1.右键项目 – C/C++ – 常规 – 附加包含目录,填写下载好的mysql.h头文件在当前电脑的路径
- 2.右键项目 – 链接器 – 常规 – 附加库目录,填写libmysql.lib的路径
- 3.右键项目 – 链接器 – 输入 – 附加依赖项,填写libmysql.lib库的名字
- 4.把 l i b m y s q l . d l l libmysql.dll libmysql.dll动态链接库(Linux下后缀名是.so库)放在工程目录下
坑点:如果MySQL装的是64位版本的,所以动态库什么的都是64位生成的,所以项目先选成64位的
MySQL接口介绍
初始化:
mysql_init()
,
要使用库,必须先进行初始化 由此也可以看出MySQL其实是网络服务,句柄就是文件描述符
MYSQL *my = mysql_init(nullptr);
链接数据库
mysql_real_connect
() :
初始化完毕之后,必须先链接数据库,再进行后续操作,因为mysql网络部分是基于TCP/IP的
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host,