Linux下的高性能轻量级Web服务器(六)

6. 使用数据库连接池

前面我们使用了线程池来避免了在处理短时间任务时创建与销毁线程的代价,保证内核的充分利用。同样的,若系统需要频繁访问数据库,则需要频繁创建和断开数据库连接,而创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。

采用数据库连接池,在程序初始化的时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,更加安全可靠。

我们首先看单个数据库连接是如何生成的:

  1. 使用 mysql_init() 初始化连接
  2. 使用 mysql_real_connect() 建立一个到mysql数据库的连接
  3. 使用 mysql_query() 执行查询语句
  4. 使用 result = mysql_store_result(mysql) 获取结果集
  5. 使用 mysql_num_fields(result) 获取查询的列数,mysql_num_rows(result)获取结果集的行数
  6. 通过 mysql_fetch_row(result) 不断获取下一行,然后循环输出
  7. 使用 mysql_free_result(result) 释放结果集所占内存
  8. 使用 mysql_close(conn) 关闭连接


对于一个数据库连接池来讲,就是预先生成多个这样的数据库连接,然后放在一个链表中,同时维护最大连接数 MaxConn,当前空闲连接数 FreeConn 和当前已用连接数 CurConn 这三个变量。同样注意在对连接池操作时(获取,释放),要用到锁机制,因为它被所有线程共享

单例模式创建

使用局部静态变量懒汉模式创建连接池类。

class connection_pool
{
public:
    //局部静态变量单例模式
    static connection_pool *GetInstance();

private:
    connection_pool();
    ~connection_pool();
}

connection_pool *connection_pool::GetInstance()
{
    static connection_pool connPool;
    return &connPool;
}



连接池代码实现

连接池定义

class connection_pool
{
public:
	MYSQL *GetConnection();		      // 获取数据库连接
	bool ReleaseConnection(MYSQL *conn);  // 释放连接
	int GetFreeConn();		      // 获取空闲连接数
	void DestroyPool();                   // 销毁所有连接

	//单例模式
	static connection_pool *GetInstance();

	void init(string url, string User, string PassWord, string DataBaseName, int Port, unsigned int MaxConn); 
	
	connection_pool();
	~connection_pool();

private:
	unsigned int MaxConn;  // 最大连接数
	unsigned int CurConn;  // 当前已使用的连接数
	unsigned int FreeConn; // 当前空闲的连接数

private:
	locker lock;
	list<MYSQL *> connList; // 连接池
	sem reserve;

private:
	string url;	         // 主机地址
	string Port;		 // 数据库端口号
	string User;		 // 登陆数据库用户名
	string PassWord;	 // 登陆数据库密码
	string DatabaseName;     // 使用数据库名
};



初始化

//构造初始化
void connection_pool::init(string url, string User, string PassWord, string DBName, int Port, unsigned int MaxConn)
{
	// 初始化数据库信息
	this->url = url;
	this->Port = Port;
	this->User = User;
	this->PassWord = PassWord;
	this->DatabaseName = DBName;

	// 创建MaxConn条数据库连接
	for (int i = 0; i < MaxConn; i++)
	{
		MYSQL *con = NULL;
		con = mysql_init(con);

		if (con == NULL)
		{
			cout << "Error:" << mysql_error(con);
			exit(1);
		}
		con = mysql_real_connect(con, url.c_str(), User.c_str(), PassWord.c_str(), DBName.c_str(), Port, NULL, 0);

		if (con == NULL)
		{
			cout << "Error: " << mysql_error(con);
			exit(1);
		}

		// 更新连接池和空闲连接数量
		connList.push_back(con);
		++FreeConn;
	}

	// 将信号量初始化为最大连接次数
	reserve = sem(FreeConn);

	this->MaxConn = FreeConn;
	
}



获取、释放连接
当线程数量大于数据库连接数量时,使用信号量进行同步,每次取出连接时,信号量减1,释放连接时,信号量加1,若连接池内没有连接了,则阻塞等待。

另外,由于多线程操作连接池,会造成竞争,这里使用互斥锁完成同步,具体的同步机制均使用lock.h中封装好的类。

// 当有请求时,从数据库连接池中返回一个可用连接,更新使用和空闲连接数
MYSQL *connection_pool::GetConnection()
{
	MYSQL *con = NULL;

	if (0 == connList.size())
		return NULL;
	
	// 取出连接,信号量原子减1,为0则等待
	reserve.wait();
	
	lock.lock();

	con = connList.front();
	connList.pop_front();

	--FreeConn;
	++CurConn;

	lock.unlock();
	return con;
}

// 释放当前使用的连接
bool connection_pool::ReleaseConnection(MYSQL *con)
{
	if (NULL == con)
		return false;

	lock.lock();

	connList.push_back(con);
	++FreeConn;
	--CurConn;

	lock.unlock();

	reserve.post();
	return true;
}



销毁连接池
通过迭代器遍历连接池链表,关闭对应数据库连接,清空链表并重置空闲连接和现有连接数量。

// 销毁数据库连接池
void connection_pool::DestroyPool()
{

	lock.lock();
	if (connList.size() > 0)
	{
		list<MYSQL *>::iterator it;
		for (it = connList.begin(); it != connList.end(); ++it)
		{
			MYSQL *con = *it;
			mysql_close(con);
		}
		CurConn = 0;
		FreeConn = 0;

		// 清空list
		connList.clear();

		lock.unlock();
	}

	lock.unlock();
}



RAII机制释放数据库连接

RAII机制的定义

RAII(Resource Acquisition Is Initialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,他说:使用局部对象来管理资源的技术称为资源获取即初始化。

这里的资源主要是指操作系统中有限的东西如内存、网络套接字等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。

通俗来说,RAII机制是一种对资源申请、释放这种成对操作的封装,通过这种方式实现在局部作用域内申请资源,使用结束后,自动释放资源



RAII类定义
这里需要注意的是,在获取连接时,通过有参构造对传入的参数进行修改。其中数据库连接本身是指针类型,所以参数需要通过双指针才能对其进行修改。

class connectionRAII{

public:
	// 双指针对MYSQL *con修改
	connectionRAII(MYSQL **con, connection_pool *connPool);
	~connectionRAII();
	
private:
	MYSQL *conRAII;
	connection_pool *poolRAII;
};



实现
不直接调用获取和释放连接的接口,将其封装起来,通过RAII机制进行获取和释放。

connectionRAII::connectionRAII(MYSQL **SQL, connection_pool *connPool){
	*SQL = connPool->GetConnection();
	
	conRAII = *SQL;
	poolRAII = connPool;
}

connectionRAII::~connectionRAII(){
	poolRAII->ReleaseConnection(conRAII);
}
posted @   夜听风雨声`  阅读(218)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示