项目背景
常见的MySQL、Oracle、SQLServer等数据库都是基于C/S架构设计的,即(客户端/服务器)架构,也就是说我们对数据库的操作相当于一个客户端,这个客户端使用既定的API把SQL语句通过网络发送给服务器端,MySQL Server执行完SQL语句后将结果通过网络返回客户端。通过网络通信的话就要涉及到TCP/IP协议里的“三次握手”、“四次挥手”等,大量访问时,每一个用户的请求都会对应一次“三次握手”、“四次挥手”的过程,这个性能的消耗是相当严重的;
对于数据库本质上是对磁盘的操作,如果对数据库的访问过多,即(I/O)操作过多,会出现访问瓶颈。
而常见的解决数据库访问瓶颈的方法有两种:
- 一、为减少磁盘 I/O的次数,在数据库和服务器的应用中间加一层 缓存数据库(例如:Redis、Memcache);
- 二、增加 连接池,来减少高并发情况下大量 TCP三次握手、MySQL Server连接认证、MySQL Server关闭连接回收资源和TCP四次挥手 所耗费的性能。
注:
目前市场上比较流行的连接池包括阿里的druid、c3p0以及apache dbcp连接池,但他们有一个共同的特点:
都是拿Java实现的。
而本项目就是采用第二种方法:
基于C++实现数据库连接池。
功能点介绍
一般的连接池都包含了数据库连接所用的ip地址、port端口号、username用户名、password密码以及其他一些性能参数:比如初始连接量、最大连接量、最大空闲时间、连接超时时间等,本项目重点实现上述通用功能:
初始连接量(initSize):
初始连接量表示连接池事先会和MySQL Server创建的initSize数量的Connection连接。在完成初始连接量之后,当应用发起MySQL访问时,不用创建新的MySQL Server连接,而是从连接池中直接获取一个连接,当使用完成后,再把连接归还到连接池中。
最大连接量(maxSize)
当并发访问MySQL Server的请求增加,初始连接量不够用了,此时会增加连接量,但是增加的连接量有个上限就是maxSIze。因为每一个连接都会占用一个socket资源,一般连接池和服务器都是部署在一台主机上,如果连接池的连接数量过多,那么服务器就不能响应太多的客户端请求了。
最大空闲时间(maxIdleTime)
当高并发过去,因为高并发而新创建的连接在很长时间(maxIdleTime)内没有得到使用,那么这些新创建的连接处于空闲,并且占用着一定的资源,这个时候就需要将其释放掉,最终只用保存iniSize个连接就行。
连接超时时间(connectionTimeOut)
当MySQL的并发访问请求量过大,连接池中的连接数量已经达到了maxSize,并且此时连接池中没有可以使用的连接,那么此时应用阻塞connectionTimeOut的时间,如果此时间内有使用完的连接归还到连接池,那么他就可以使用,如果超过这个时间还是没有连接,那么它获取数据库连接池失败,无法访问数据库。
关键技术点
- 单例模式:
连接池只需要一个实例,故使用单例模式来实现,实现逻辑如下:
class ConnectionPool
{
public:
// 获取连接池的对象实例
static ConnectionPool* getConnectionPool();
};
// 线程安全的懒汉单例函数接口
ConnectionPool* ConnectionPool::getConnectionPool()
{
// 当程序运行到此时才会生成对象实例
static ConnectionPool pool;
return &pool;
}
- queue容器:
连接池的数据结构是queue队列,最早生成的连接connection放在队头,此时记录一个起始时间,这一点在后面最大空闲时间时会发挥作用:如果队头都没有超过最大空闲时间,那么其他的一定没有。实现逻辑如下:
#include <queue>
class ConnectionPool
{
private:
// 存储mysql连接的队列
queue<Connection*> _connectionQue;
};
// 创建初始数量的连接(构造函数初始化时)
for (int i = 0; i < _initSize; ++i)
{
// 创建一个新的连接
Connection* p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
// 刷新一下开始空闲的起始时间
p->refreshAliveTime();
// 将这个新的连接放入连接池队列
_connectionQue.push(p);
// 连接数量加一
_connectionCnt++;
}
// 连接数量没有达到上限,继续创建新的连接(并发量增大需要扩增连接时)
if (_connectionCnt < _maxSize)
{
Connection* p = new Connection();
p->connect(_ip, _port, _username, _password, _dbname);
// 刷新一下开始空闲的起始时间
p->refreshAliveTime();
// 将这个新的连接放入连接池队列
_connectionQue.push(p);
// 连接数量加一
_connectionCnt++;
}
// 扫描整个队列,释放多余的连接(高并发过后,新建的连接超过最大超时时间时)
unique_lock<mutex> lock(_queueMutex);
while (_connectionCnt > _initSize)
{
Connection* p = _connectionQue.front();
if (p->getAliveTime() >= (_maxIdleTime * 1000))
{
_connectionQue.pop();
_connectionCnt--;
// 调用~Connection()释放连接
delete p;
}
else
{
// 如果队头的连接没有超过_maxIdleTime,其他连接肯定没有
break;
}
}
- C++11多线程编程:
为了进行压力测试,用结果证明使用连接池之后对数据库的访问效率确实比不使用连接池的时候高很多,使用了多线程来进行数据库的访问操作,并且观察多线程下连接池对于性能的提升。实现逻辑如下:
#include <thread>
int main()
{
thread t1([]() {
for (int i = 0; i < 250; ++i)
{
Connection conn;
char sql[1024] = {
0 };
sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
"zhang san", 20, "male");
conn.connect("127.0.0.1", 3306, "root", "31777", "chat");
conn.update(sql);
}
});
thread t2([]() {
for (int i = 0; i < 250; ++i)
{
Connection conn;
char sql[1024] = {
0 };
sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')",
"zhang san", 20, "male");
conn.connect("127.0.0.1", 3306, "root", "31777", "chat");
conn.update(sql);
}
});
thread t3

最低0.47元/天 解锁文章
2万+

被折叠的 条评论
为什么被折叠?



