数据库连接池的设计实现

226 阅读3分钟

连接池的设计实现

一个资源满足以下两个条件,就可以考虑使用池化技术

  • 创建开销大
  • 可重复使用

如线程池、内存池、mysql与oracle连接池

连接池

连接池

连接池往往和线程池搭配使用,连接池一般采用链表存储(是不是有点类似线程池中的消息队列)

20230205171009

连接池中的连接需要根据线程数量设置

为什么需要连接池:比如注册个账号,需要以下过程

20230205171401

获取连接 =》 执行sql =》 归还连接

API

连接:都是进程和进程间的,不过这里有个进程是数据库进程

以mysql连接为例,它是指服务器A进程a和服务器B进程b(数据库的ip和端口,如果地址为127.0.0.1,那么就是本地数据库)之间的连接,假设一个客户端访问服务器A的a进程,a由于已经和服务器B的数据库的进程b建立了连接,所以a中线程池的某一个线程可以去连接池获取这两个进程间的连接,访问数据库后响应客户端,这样就省去了TCP建立连接和Mysql认证这两个操作了。

tip:

  • 由于存在多进程和多个数据库,连接池可能不止一个
  • 这里的连接是长连接
  • 而当访问的是本机的数据库时(127.0.0.1,这个地址发请求应该是是不需要走网卡的,因为接收请求的协议栈就是本协议栈,那就没必要三次握手了,自己难道还不认识自己),所以这种情况用连接池性能提升不会特别大(但因为省去了mysql认证操作,性能提升了一倍左右)
  1. 连接池的构造与析构函数:
// 构造赋值
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)
{
	m_pool_name = pool_name;     // 数据库连接名
	m_db_server_ip = db_server_ip;   // 服务器ip和端口
	m_db_server_port = db_server_port;
	m_username = username;   // 数据库用户名和密码
	m_password = password;
	m_db_name = db_name;    // 数据库名
	m_db_max_conn_cnt = max_conn_cnt;	// 最大连接数
	m_db_cur_conn_cnt = MIN_DB_CONN_CNT; // 最小连接数量
}

// 释放连接池
CDBPool::~CDBPool()
{
	std::lock_guard<std::mutex> lock(m_mutex);
	m_abort_request = true;
	m_cond_var.notify_all();		// 通知所有在等待的线程

    // 如果有连接没有返回来,有泄漏的问题
    // 所以尽量先销毁线程池,再销毁连接池
	for (list<CDBConn *>::iterator it = m_free_list.begin(); it != m_free_list.end(); it++)
	{
		CDBConn *pConn = *it;
		delete pConn;
	}

	m_free_list.clear();
}
20230205193004
  1. 初始化:
20230205173504
bool CPrepareStatement::Init(MYSQL *mysql, string &sql)
{
	mysql_ping(mysql);	// 当mysql连接丢失的时候,使用mysql_ping能够自动重连数据库

	//g_master_conn_fail_num ++;
	m_stmt = mysql_stmt_init(mysql);
	if (!m_stmt)
	{
		log_error("mysql_stmt_init failed\n");
		return false;
	}

	if (mysql_stmt_prepare(m_stmt, sql.c_str(), sql.size()))
	{
		log_error("mysql_stmt_prepare failed: %s\n", mysql_stmt_error(m_stmt));
		return false;
	}

	m_param_cnt = mysql_stmt_param_count(m_stmt);
	if (m_param_cnt > 0)
	{
		m_param_bind = new MYSQL_BIND[m_param_cnt];
		if (!m_param_bind)
		{
			log_error("new failed\n");
			return false;
		}

		memset(m_param_bind, 0, sizeof(MYSQL_BIND) * m_param_cnt);
	}

	return true;
}
  1. 请求获取连接:
20230205173805
/*
 * timeout_ms默认为 0死等
 * timeout_ms >0 则为等待的时间
 */
int wait_cout = 0;
CDBConn *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)		// A. 如果没设置超时时间,死等:直到有连接可以用 或者 连接池要释放
			{
				log_info("wait ms:%d\n", timeout_ms);
				m_cond_var.wait(lock, [this] 
				{
					// 当前连接数量小于最大连接数量 或者请求释放连接池时退出
					return (!m_free_list.empty()) | m_abort_request;
				});
			} else {
				// B. 设置了超时时间,超时等待:
				// 1.在超时时间内有连接可以用
				// 2. 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 // 还没有到最大连接则创建连接
		{
			CDBConn *pDBConn = new CDBConn(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++;
			}
		}
	}

	CDBConn *pConn = m_free_list.front();	// 获取连接
	m_free_list.pop_front();	// 从空闲队列删除
	pConn->setCurrentTime();  
	m_used_list.push_back(pConn);		// 加入使用队列

	return pConn;
}
  1. 归还连接:
20230205192350
void CDBPool::RelDBConn(CDBConn *pConn)
{
	std::lock_guard<std::mutex> lock(m_mutex);

	list<CDBConn *>::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");
	}
}