数据库连接池(C++11实现)

目的:

        因为对数据库的操作实质上是对磁盘的IO操作,所以如果对数据库访问次数过多,就会到导致大量的磁盘IO,为了提高MySQL数据库(基于C/S设计)的访问瓶颈,除了在服务器端增加缓存服务器缓存常用的数据 之外(例如Redis),还可以增加连接池,来提高MySQL Server的访问效率。

        在高并发情况下,大量的 TCP三次握手、MySQL Server连接认证、MySQL Server关闭连接回收资源和TCP四次挥手所耗费的时间也十分明显,增加连接池就是为了减少这一部分的性能损耗。

        在系统启动时就创建一定数量的连接,用户一旦执行CURD操作,直接拿出一条连接即可,不需要TCP的连接过程和资源回收过程,使用完该连接后归还给连接池的连接队列,供之后使用。

功能介绍:

 1、初始连接数(initSize):
        初始连接量表示连接池事先会和MySQL Server创建的initSize数量的SqlConn连接。在完成初始连接量之后,当应用发起MySQL访问时,不用创建新的MySQLServer连接,而是从连接池中直接获取一个连接,当使用完成后,再把连接归还到连接池中。
2、最大容量(maxCapacity)
        连接池的最大容量即连接池内的最大连接数。当并发访问MySQL Server的请求增多时,初始连接量不够使用时,会根据新的请求数量去创建更多的连接给应用去使用,但是新创建的连接数量上限是maxCapacity,不能无限制的创建连接,因为每一个连接都会占用一个socket资源,一般连接池和服务器程序是部署在一台 主机上的,如果连接池占用过多的socket资源,那么服务器就不能接收太多的客户端请求了。当这些连接使用完成后,会再次归还到连接池当中来维护。
3、最大空闲时间(maxIdleTime)
        当高并发过去,因为高并发而新创建的连接在很长时间(maxIdleTime)内没有得到使用,那么这些新创建的连接处于空闲状态,并且占用着一定的资源,这个时候就需要将其释放掉,最终只用保存initSize个连接就行。
4、连接超时时间(connTimeOut)
        当MySQL的并发访问请求量过大,连接池中的连接数量已经达到了maxSize,并且此时连接池中没有可以使用的连接,那么此时应阻塞connTimeOut的时间,如果此时间内有使用完的连接归还到连接池,那么就可以使用,如果超过这个时间还是没有连接,那么获取数据库连接就失败,无法访问数据库。

运行流程:

1、使用单例模式(局部静态变量方法)获取连接池实例。

SqlConnPool* pool = SqlConnPool::getInstance();

2、从SqlConnPool中获取和Mysql的连接SqlConn,为了实现RAII的目标,将getConn()函数的返回值设置为shared_ptr<SqlConn>类型。

shared_ptr<SqlConn> sp = pool->getConn();

3、利用获取的连接,对数据库进行CURD操作。

sp->update(sql);
sp->query(sql);

SqlConn类结构         SqlConn.h

#ifndef SQLCONN_H
#define SQLCONN_H

#include <mysql/mysql.h>
#include <chrono>
#include <string>

class SqlConn
{
public:
	SqlConn();
	~SqlConn();
	//建立Sql连接
	bool connect(const std::string& ip,
				 const uint16_t port, 
				 const std::string& user, 
				 const std::string& pwd,
                 const std::string& dbName);

	// 更新操作 insert、delete、update
	bool update(const std::string& sql);
	// 查询操作 select
	MYSQL_RES* query(const std::string& sql);
    //刷新存活时间
	void refreshAliveTime()
	{
		aliveTime = std::chrono::steady_clock::now();
	}
    //获取存活时间间隔
	size_t getAliveTime()
	{
		return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - aliveTime).count();
	}
private:
	MYSQL* conn;//数据库连接
	std::chrono::time_point<std::chrono::steady_clock> aliveTime; //连接存活时间
};

#endif // !SQLCONN_H

Sqlconn实现        Sqlconn.cpp

#include "sqlconn.h"

SqlConn::SqlConn()
{
    conn=mysql_init(nullptr);
    mysql_set_character_set(conn, "utf8");
}

SqlConn::~SqlConn()
{
    if(conn!=nullptr)
    {
        mysql_close(conn);
    }
}

bool SqlConn::connect(const std::string& ip,const uint16_t port,const std::string& user,const std::string& pwd,const std::string& dbName)
{
    MYSQL* ptr=mysql_real_connect(conn,ip.c_str(),user.c_str(),pwd.c_str(),dbName.c_str(),port,nullptr,0);
    return ptr!=nullptr;
}

bool SqlConn::update(const std::string& sql)
{
    if(mysql_query(conn,sql.c_str()))
    {
        return false;       
    }
    return true;
}

MYSQL_RES* SqlConn::query(const std::string& sql) 
{
    if(mysql_query(conn, sql.c_str())) 
    {
        return nullptr;
    }
    return mysql_use_result(conn);
}

注:mysql_query()函数有两种返回值,如果执行的是更新操作(insert、delete、update),则返回 true/ false;如果执行的是查询操作(select),则返回 MYSQL_RES* 类型。
 

SqlConnPool类结构         SqlConnPool.h

#ifndef SQLCONNPOOL_H
#define SQLCONNPOOL_H

#include <mysql/mysql.h>
#include <memory>
#include <string>
#include "sqlconn.h"
#include "blockqueue.h"
#include "log.h"

class SqlConnPool
{
public:
    static SqlConnPool* getInstance(int maxCapacity=1024)
    {
        static SqlConnPool single(maxCapacity);
        return &single;
    }
    std::shared_ptr<SqlConn> getConn();
    
private:
    SqlConnPool(int maxCapacity);
    ~SqlConnPool(){};
    SqlConnPool(const SqlConnPool& other)=delete;
    SqlConnPool& operator =(const SqlConnPool& other)=delete;
    //加载配置文件
    bool loadConfigFile();
    //专门用于产生新连接的线程
    void produceConnTask();
    //扫描超过最大空闲时间maxIdleTime的连接,对其进行回收
    void scannerConnTask();
    //添加新连接
    void addConn();

    std::string ip;
    uint16_t port;
    std::string user;
    std::string pwd;
    std::string dbName;

    size_t initSize;//初始连接数
    size_t maxIdleTime;//最大空闲时间
    size_t connTimeout;//超时时间

    BlockQueue<SqlConn*> connQue;
};

#endif // !SQLCONNPOOL_H

SqlConnPool实现        SqlConnPool.cpp

#include "sqlconnpool.h"
#include <functional>
using std::string;

bool SqlConnPool::loadConfigFile()//加载mysql.ini配置文件
{
    FILE* fp = fopen("./mysql.ini", "r");
    if (fp == nullptr)
    {
        LOG_ERROR("%s","mysql.ini file dose not exsit!");
        return false;
    }    
    while(!feof(fp)) //循环读取配置文件的每一行
    {
    	char line[1024] = {0};
        fgets(line, 1024, fp);
        string str = line;
        int idx = str.find('=', 0);
        if (idx == -1)
        {
            continue;
        }
        int endidx = str.find('\n', idx);
        string key = str.substr(0, idx);//截取key
        string value = str.substr(idx+1,endidx-idx-1);//截取value

        if (key == "ip")    
            ip = value;
        else if (key == "port")
            port = stoi(value);
        else if (key == "username")
            user = value;
        else if (key == "password") 
            pwd = value;
        else if (key == "dbName")
            dbName = value;
        else if (key == "initSize")
            initSize = stoi(value);
        else if (key == "maxIdleTime")
			maxIdleTime = stoi(value);
		else if (key == "connTimeOut")
			connTimeout = stoi(value);
    }
    fclose(fp);
    return true;
}

 void SqlConnPool::addConn()//添加Sql连接
 {
    SqlConn* conn=new SqlConn();
    conn->connect(ip,port,user,pwd,dbName);
    conn->refreshAliveTime();
    connQue.push_back(conn);
 }

//构造SqlConnPool,初始化connQue变量
SqlConnPool::SqlConnPool(int maxCapacity):connQue(maxCapacity)
{
    if (!loadConfigFile())//配置失败
    {
        return ;
    }
    for (size_t i = 0; i < initSize; i++)//添加初始initSize数量的连接
    {
        addConn();
    }
    //初始化日志系统(默认为异步模式)
    Log::getInstance()->init();
    //生产者线程
    std::thread produce(std::bind(&SqlConnPool::produceConnTask, this));
    produce.detach();
    //扫描线程
    std::thread scanner(std::bind(&SqlConnPool::scannerConnTask, this));
    scanner.detach();
}

void SqlConnPool::produceConnTask()
{
    for(;;)
    {
        addConn();
    }
}

void SqlConnPool::scannerConnTask()
{
    for (;;)
    {
        std::this_thread::sleep_for(std::chrono::microseconds(maxIdleTime));
        while (connQue.get_size() > initSize)//清除超出最大空闲时间的连接
        {
            SqlConn* p = connQue.front();
            if(p->getAliveTime() >= maxIdleTime)
            {
                SqlConn* tmp=nullptr;
                connQue.pop(tmp);
                delete tmp;
            }
            else 
                break;
        }
    }
}

std::shared_ptr<SqlConn> SqlConnPool::getConn()//获取一个Sql连接
{
    SqlConn* conn;
    if (!connQue.pop(conn,connTimeout))//超过连接超时时间
    {
        LOG_ERROR("%s","get connection timeout");
        return nullptr;
    }
   std::shared_ptr<SqlConn> sp(conn,
          [&](SqlConn* p) 
          {
              p->refreshAliveTime();
              connQue.push_back(p);
          });//自定义删除函数(删除时将连接再次放入阻塞队列中,重复使用)
    return sp;
}

测试程序        testSqlConnPool.cpp

四种试验:多线程无连接池、多线程有连接池、单线程无连接池、单线程有连接池

分别测试其“建立10000个数据库连接,每个连接执行一次插入语句”的运行时间。

#include "sqlconn.h"
#include "sqlconnpool.h"
#include <time.h>
#include <iostream>
using namespace std;

void SigleWithConnection()//单线程有连接池
{
	time_t begin = clock();
	for (int i = 0; i < 10000; ++i)
	{
		SqlConnPool* pool = SqlConnPool::getInstance();

		shared_ptr<SqlConn> sp = pool->getConn();
		char sql[1024] = {0};
		sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
		sp->update(sql);
	}
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}

void SigleNoConnection()//单线程无连接池
{
	time_t begin = clock();
	for (int i = 0; i < 10000; ++i)
	{
		SqlConn conn;
		char sql[1024] = {0};
		sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
		conn.connect("127.0.0.1", 3306, "root", "123456", "yourdb");
		conn.update(sql);
	}
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}


void runInThreadNoConn()
{   
    for (int i = 0; i < 2500; ++i)
		{
			SqlConn conn;
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
			conn.connect("127.0.0.1", 3306, "root", "123456", "yourdb");
			conn.update(sql);
		}
}

void MutiNoConnection()//多线程无连接池
{
	SqlConn conn;
	conn.connect("127.0.0.1", 3306, "root", "123456", "yourdb");
	time_t begin = clock();
	for(int i=0;i<4;i++)
    {
        thread t=thread(runInThreadNoConn);
        t.join();     
    }
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}

void runInThreadByConn()
{   
    for (int i = 0; i < 2500; i++)
	{
		SqlConnPool* pool = SqlConnPool::getInstance();
        shared_ptr<SqlConn> sp = pool->getConn();
		char sql[1024] = {0};
		sprintf(sql, "insert into user(username,password) values('%s','%s')","zhangsan","123456");
		if (sp == nullptr)
		{
			cout << "sp is empty" << endl;
			continue;
		}
		sp->update(sql);
	}
}

void MutiWithConnection()//多线程有连接池
{
	time_t begin = clock();
	for(int i=0;i<4;i++)
    {
        thread t=thread(runInThreadByConn);
        t.join();     
    }
	time_t end = clock();
	cout << (end - begin)/1000 << " ms" << endl;
}

int main()
{ 
	MutiNoConnection();//多线程无连接池
	MutiWithConnection();//多线程有连接池
	SigleNoConnection();//单线程无连接池
	SigleWithConnection();//单线程有连接池
	return 0;
}

测试结果

 由结果可知,使用多线程+连接池的组合有效提高了数据库的访问效率。

  • 数据量10000,单线程从5280ms变成2080ms
  • 数据量10000,多线程从2140ms变成了1910ms

附:

配置文件        mysql.ini

# 数据库连接池配置文件
ip=127.0.0.1
port=3306
username=root
password=123456
dbName=yourdb
#初始连接数
initSize=4
# 最大空闲时间,单位ms
maxIdleTime=6000
# 连接超时时间,单位ms
connTimeOut=100

Makefile

CXX = g++
CFLAGS = -std=c++14 -O2 -Wall -g 

TARGET = testSqlConnPool
OBJS =  buffer.cpp log.cpp blockqueue.h\
		sqlconn.cpp sqlconnpool.cpp\
        testSqlConnPool.cpp

all: $(OBJS)
	$(CXX) $(CFLAGS) $(OBJS) -o $(TARGET)  -pthread -L/usr/lib64/mysql -lmysqlclient

clean:
	rm -rf $(OBJS) $(TARGET)

阻塞队列实现

基于C++11实现的阻塞队列(BlockQueue)_{(sunburst)}的博客-优快云博客

删去代码中的LOG_XXXX语句,就无需链接 log.cpp,且无需链接仅与日志系统有关的 buffer.cpp 

日志系统实现

同步+异步日志系统(C++实现)_{(sunburst)}的博客-优快云博客

缓冲区实现

缓冲区Buffer类的设计(参考Muduo实现)_{(sunburst)}的博客-优快云博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值