MySQL 数据库连接池
关键技术点:
- MySQL 数据库编程
- 单例模式
- queue队列容器
- C++11多线程编程、线程互斥、线程同步通信和 unique_lock
- 基于CAS的原子整形
- 智能指针shared_ptr
- lambda表达式
- 生产者-消费者线程模型
项目背景
为了提高 MySQL 数据库(基于C/S设计)的访问瓶颈,除了在服务器端增加缓存服务器缓存常用的数据之外(例如:redis),还可以增加连接池,来提高 MySQL Server 的访问效率,在高并发情况下,大量的 TCP 三次握手、 MySQL Server 连接认证、MySQL Server 关闭连接回收资源和 TCP 四次挥手所耗费的性能时间也是很明显的,增加连接池就是为了减少这一部分的性能损耗。
而常见的解决数据库访问瓶颈的方法有两种:
- 一、为
减少磁盘 I/O
的次数,在数据库和服务器的应用中间加一层 缓存数据库(例如:Redis、Memcache); - 二、增加
连接池
,来减少高并发情况下大量 TCP三次握手、MySQL Server连接认证、MySQL Server关闭连接回收资源和TCP四次挥手 所耗费的性能。
注:
在市场上比较流行的连接池包括阿里的 druid,c3p0 以及 apache dbcp 连接池,它们对于短时间内大量的数据库增删改查操作性能的提升是很明显的,但是它们有一个共同点就是,全部由 Java 实现的。
那么本项目就是为了在 C/C++ 项目中,提供 MySQL Server 的访问效率,实现基于 C++ 代码的数据库连接 池模块。
功能介绍
连接池一般包含了数据库连接所用的 ip 地址、port 端口、用户名和密码以及其他的性能参数,例如:初始连接量、最大量接量、最大空闲时间、连接超时时间等等,该项目是基于 C++ 语言实现的连接池,主要也是实现以上几个所有连接池都支持的通用基础功能。
- 初始连接量(iniSize):表示连接池实现和 MySQL Server 创建 iniSize 个数的 connection 连接,当应用发起 MySQL 访问时,不用再创建和 MySQL Server 新的连接,直接从连接池中获取一个可用的连接即可,使用完成后,并不去释放 connection,而是把当前 connection 在归还到连接池中。
- 最大量接量(maxSize):当并发访问 MySQL Server 的请求增多时,初始连接量已经不够使用时,此时会根据新的请求数量创建更多的连接给应用去使用,但是新创建的连接数量上限是 maxSize,不能无限制的创建连接,因为每一个连接都会占用一个 socket 资源,一般连接池和服务器程序是部署在一台主机上的,如果连接池占用过多 socket 资源,那么服务器就不能接受太多的客户端请求了。当这些连接使用完成后,再次归还到连接池中来维护。
- 最大空闲时间(maxIdleTime):当访问 MySQL 的并发请求多了以后,连接池里面的连接数量会动态增加,上限是 maxSize 个,当这些连接用完再次归还到连接池当中。如果在指定的 maxIdleTime 里面,这些新增加的连接都没有被再次使用过,那么新增加的这些连接资源就要被回收掉,只需要保持初始连接量 initSize 个连接就可以了。
- 连接超时时间(connectionTimeout):当 MySQL 的并发请求量过大,连接池中的连接数量已经到达 maxSize 了,而此时没有空闲的连接可供使用,那么此时应用从连接池获取连接无法成功,它通过阻塞的方式获取连接的时间如果超过 connectionTimeout 时间,那么获取连接失败,无法访问数据库。
MySQL Server 参数介绍
1 |
|
该命令可以查看 MySQL Server 所支持的最大连接个数,超过 max_connections
数量的连接,MySQL Server 会直接拒绝,所以在使用连接池增加连接数量的时候,MySQL Server 的 max_connections
参数也要适当的进行调整,以适配连接池的连接上限。
功能实现设计
ConnectionPool.cpp
和 ConnectionPool.h
:连接池代码实现 Connection.cpp
和 Connection.h
:数据库操作代码、增删改查代码实现
连接池主要包含了以下功能点:
- 连接池只需要一个实例,所以 ConnectionPool 以单例模式进行设计
- 从 ConnectionPool 中可以获取和 MySQL 的连接 Connection
- 空闲连接 Connection 全部维护在一个线程安全的 Connection 队列中,使用线程互斥锁保证队列的线程安全
- 如果 Connection 队列为空,还需要再获取连接,此时需要动态创建连接,上限数量是 maxSize
- 队列中空闲连接时间超过 maxIdleTime 的就要被释放掉,只保留初始的 initSize 个连接就可以了,这个功能点需要放在独立的线程中去做
- 如果 Connection 队列为空,而此时连接的数量已达上限 maxSize,那么等待 connectionTimeout 时间如果还获取不到空闲的连接,那么获取连接失败,此处从 Connection 队列获取空闲连接,可以使用带超时时间的 mutex 互斥锁来实现连接超时时间
- 用户获取的连接用 shared_ptr 智能指针来管理,用 lambda 表达式定制连接释放的功能(不真正释放连接,而是把连接归还到连接池中)
- 连接的生产和连接的消费采用生产者-消费者线程模型来设计,使用了线程间的同步通信机制条件变量和互斥锁
开发平台选型
有关 MySQL 数据库编程、多线程编程、线程互斥和同步通信操作、智能指针、设计模式、容器等等这些技术在 C++ 语言层面都可以直接实现,因此该项目选择直接在 windows 平台上进行开发,当然放在 Linux 平台下用 g++ 也可以直接编译运行。
压力测试
验证数据的插入操作所花费的时间,第一次测试使用普通的数据库访问操作,第二次测试使用带连接池的数据库访问操作,对比两次操作同样数据量所花费的时间,性能压力测试结果如下:
数据量 | 未使用连接池花费时间 | 使用连接池花费时间 |
---|---|---|
1000 | 单线程: 四线程: | 单线程: 四线程: |
5000 | 单线程: 四线程: | 单线程: 四线程: |
10000 | 单线程: 四线程: | 单线程: 四线程: |
MySQL 数据库编程
这里的 MySQL 数据库编程直接采用 oracle 公司提供的 MySQL C/C++
客户端开发包,在 VS 上需要进行相应的头文件和库文件的配置,如下:
- 右键项目 - C/C++ - 常规 - 附加包含目录,填写mysql.h头文件的路径
找到 mysql.h
目录文件
在 VS 编译器设置中的 C/C++ - 常规 中包含该目录
- 右键项目 - 链接器 - 常规 - 附加库目录,填写libmysql.lib的路径
找到 libmysql.lib
根目录
在 VS 编译器设置中的 链接器 - 常规 中包含该目录
- 右键项目 - 链接器 - 输入 - 附加依赖项,填写libmysql.lib库的名字
- 把libmysql.dll动态链接库(Linux下后缀名是.so库)放在工程目录下
代码实现
1. 配置 MySQL
Connection.h
1 |
|
Connection.cpp
1 |
|
Common.h
1 |
|
main.cpp
1 |
|
2. 连接池单例实现
Connection.h
1 |
|
ConnectionPool.cpp
1 |
|
3. 加载配置文件实现
mysql.ini
1 |
|
Connection.h
1 |
|
ConnectionPool.cpp
1 |
|
4. 创建链接的生产者线程
ConnectionPool.h
1 |
|
ConnectionPool.cpp
1 |
|
5. 创建连接的消费者线程
ConnectionPool.h
1 |
|
ConnectionPool.cpp
1 |
|
6. 最大空闲时间
Connection.h
1 |
|
ConnectionPool.cpp
1 |
|
7. 回收超时连接线程
ConnectionPool.h
1 |
|
ConnectionPool.cpp
1 |
|
连接池压力测试
1 |
|