高并发服务器-连接池实现(c++版本)

前言

记得鲁迅说过,天下武功,维快不破!
但记得鲁迅还说过,男人不能轻易说自己很快!
是不是经常听到我们的系统是ns级别的响应,夸张了,哦不对是毫秒。
是不是经常遇到这种池那种池,为啥要用他们?说白了,就是为了快!

池化技术

池化技术减少了各种对象的创建和销毁的开销,间接提高了服务响应的速度。
其实我们常见的内存池/连接池/线程池,基本都是为了提高速度来的。
这是这些池化技术的共同特点:
1. 减少了对象创建时间
2. 减少对象创建消耗大量的资源
3. 对象创建后可被重复使⽤

数据库连接池技术

我们都知道,数据库的连接数量是有限制,创建和释放的开销并不小,如果我们在程序启动时,就进行数据库的连接的创建,后面如果需要使用到的地方,直接从连接池中获取使用,这样,我们的连接不但复用了,也减少了频繁操作数据库的连接导致的一些其他问题。

如果假设你不使用连接池,每个使用到数据库的地方,都取创建连接,发送数据,关闭连接,这样存在的以下三个问题:

1、连接和释放需要时间,三次握手四次挥手,加上中间的时间消耗,系统性能并不好

2、关闭连接在服务端会导致出现Time_Wait状态的TCP链接

3、管理维护混乱,依赖程序员的水平,不能做到组件化。

数据库连接池实现

连接池需要的数据结构,我们知道,至少要拥有一个连接池管理器,

对外的接口:

1、初始化池:包括对数据库的连接,连接数管理

2、获取连接:外部可通过池获取连接

3、归还连接:外部使用完连接后要归还

连接池内部应该实现对连接信息的管理。

以下提供了一种大概的实现方式:

class CDBPool {	
public:
	CDBPool() {}
	CDBPool(const char* pool_name, const char* db_server_ip, uint16_t db_server_port,
			const char* username, const char* password, const char* db_name, 
			int max_conn_cnt, LOGHANDLE pLogHandle);
	virtual 	~CDBPool();

	int 		Init();		// 连接数据库,创建连接
	CDBConnect* GetDBConn(const int timeout_ms = -1);	// 获取连接资源
	void 		RelDBConn(CDBConnect* pConn);	// 归还连接资源

	const char* GetPoolName() { return m_pool_name.c_str(); }
	const char* GetDBServerIP() { return m_db_server_ip.c_str(); }
	uint16_t 	GetDBServerPort() { return m_db_server_port; }
	const char* GetUsername() { return m_username.c_str(); }
	const char* GetPasswrod() { return m_password.c_str(); }
	const char* GetDBName() { return m_db_name.c_str(); }

private:
	string 		m_pool_name;				// 连接池名称
	string 		m_db_server_ip;				// 数据库ip
	uint16_t	m_db_server_port;			// 数据库端口
	string 		m_username;  				// 用户名
	string 		m_password;					// 用户密码
	string 		m_db_name;					// db名称
	int			m_db_cur_conn_cnt;			// 当前启用的连接数量
	int 		m_db_max_conn_cnt;			// 最大连接数量
	list<CDBConnect*>	m_free_list;		// 空闲的连接
	list<CDBConnect*>	m_used_list;		// 记录已经被请求的连接
	std::mutex m_mutex;
    std::condition_variable m_cond_var;
	bool m_abort_request = false;

	LOGHANDLE	m_pLog;
	// CThreadNotify	m_free_notify;		// 信号量
};

其中维持了两个队列,一个空闲的队列还有一个请求的连接队列。

获取连接的实现:

CDBConnect *CDBPool::GetDBConn(const int timeout_ms)
{
	std::unique_lock<std::mutex> lock(m_mutex);
	if(m_abort_request) 
	{
		log_warn("have aboort\n");
		return NULL;
	}

	if (m_free_list.empty())		// 当没有连接可以用时
	{
		// 第一步先检测 当前连接数量是否达到最大的连接数量 
		if (m_db_cur_conn_cnt >= m_db_max_conn_cnt)
		{
			// 如果已经到达了,看看是否需要超时等待
			if(timeout_ms < 0)		// 死等,直到有连接可以用 或者 连接池要退出
			{
				log_info("wait ms:%d\n", timeout_ms);
				m_cond_var.wait(lock, [this] 
				{
					// log_info("wait:%d, size:%d\n", wait_cout++, m_free_list.size());
					// 当前连接数量小于最大连接数量 或者请求释放连接池时退出
					return (!m_free_list.empty()) | m_abort_request;
				});
			} else {
				// return如果返回 false,继续wait(或者超时),  如果返回true退出wait
				// 1.m_free_list不为空
				// 2.超时退出
				// 3. m_abort_request被置为true,要释放整个连接池
				m_cond_var.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] {
					// log_info("wait_for:%d, size:%d\n", wait_cout++, m_free_list.size());
					return (!m_free_list.empty()) | m_abort_request;
				});
				// 带超时功能时还要判断是否为空
				if(m_free_list.empty()) 	// 如果连接池还是没有空闲则退出
				{
					return NULL;
				}
			}

			if(m_abort_request) 
			{
				log_warn("have aboort\n");
				return NULL;
			}
		}
		else // 还没有到最大连接则创建连接
		{
			CDBConnect *pDBConn = new CDBConnect(this);	//新建连接
			int ret = pDBConn->Init();
			if (ret)
			{
				log_error("Init DBConnecton failed\n\n");
				delete pDBConn;
				return NULL;
			}
			else
			{
				m_free_list.push_back(pDBConn);
				m_db_cur_conn_cnt++;
				log_info("new db connection: %s, conn_cnt: %d\n", m_pool_name.c_str(), m_db_cur_conn_cnt);
			}
		}
	}

	CDBConnect *pConn = m_free_list.front();	// 获取连接
	m_free_list.pop_front();	// STL 吐出连接,从空闲队列删除
	// pConn->setCurrentTime();  // 伪代码
	m_used_list.push_back(pConn);		// 

	return pConn;
}

归还连接的实现:

void CDBPool::RelDBConn(CDBConnect *pConn)
{
	std::lock_guard<std::mutex> lock(m_mutex);

	list<CDBConnect *>::iterator it = m_free_list.begin();
	for (; it != m_free_list.end(); it++)	// 避免重复归还
	{
		if (*it == pConn)	
		{
			break;
		}
	}

	if (it == m_free_list.end())
	{
		m_used_list.remove(pConn);
		m_free_list.push_back(pConn);
		m_cond_var.notify_one();		// 通知取队列
	} else 
	{
		log_error("RelDBConn failed\n");
	}
}

连接对象应该如何实现

 应该对外要实现的接口:

1、增删改查。

2、事务操作,启动,回滚等操作。

对内的实现:与真正的MYSQL对象进行交互。

实现的代码:

class CDBConnect {
public:
	CDBConnect(CDBPool* pDBPool);
	virtual ~CDBConnect();
	int Init();

	// 创建表
	bool ExecuteCreate(const char* sql_query);
	// 删除表
	bool ExecuteDrop(const char* sql_query);
	// 查询
	CResultSet* ExecuteQuery(const char* sql_query);

    /**
    *  执行DB更新,修改
    *
    *  @param sql_query     sql
    *  @param care_affected_rows  是否在意影响的行数,false:不在意;true:在意
    *
    *  @return 成功返回true 失败返回false
    */
	bool ExecuteUpdate(const char* sql_query, bool care_affected_rows = true);
	uint32_t GetInsertId();

	// 开启事务
	bool StartTransaction();
	// 提交事务
	bool Commit();
	// 回滚事务
	bool Rollback();
	// 获取连接池名
	const char* GetPoolName();
	MYSQL* GetMysql() { return m_mysql; }
private:
	CDBPool* 	m_pDBPool;	// to get MySQL server information
	MYSQL* 		m_mysql;	// 对应一个连接
	char		m_escape_string[MAX_ESCAPE_STRING_LEN + 1];
};

因为都是对mysql对象的操作,我就简单挑一个接口的实现来举例

CResultSet* CDBConnect::ExecuteQuery(const char* sql_query)
{
	mysql_ping(m_pmySql);

	if (mysql_real_query(m_pmySql, sql_query, strlen(sql_query)))
	{
		LogError(m_pLog,"mysql_real_query failed: %s, sql: %s\n", mysql_error(m_pmySql), sql_query);
		return NULL;
	}
	// 返回结果
	MYSQL_RES* res = mysql_store_result(m_pmySql);	// 返回结果
	if (!res)
	{
		LogError(m_pLog,"mysql_store_result failed: %s\n", mysql_error(m_pmySql));
		return NULL;
	}

	CResultSet* result_set = new CResultSet(res);	// 存储到CResultSet
	return result_set;
}

其中,这是由返回结果的,我们通常要对mysql的返回结果转换为我们自己想要的数据结构如下

// 返回结果 select的时候用
class CResultSet {
public:
	CResultSet(MYSQL_RES* res);
	virtual ~CResultSet();

	bool Next();
	int GetInt(const char* key);
	char* GetString(const char* key);
private:
	int _GetIndex(const char* key);

	MYSQL_RES* m_res;
	MYSQL_ROW			m_row;
	map<string, int>	m_key_map;
};

这样,应用层获取到的结构就可以直接操作。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值