数据库连接池的实现与原理

池化技术

池化技术能够减少资源对象的创建次数,提高程序的响应性能,特别是在高并发下这种提高更加明显。
使用池化技术缓存的资源对象有如下共同特点:
1. 对象创建时间长;
2. 对象创建需要大量资源;
3. 对象创建后可被重复使用
像常见的线程池、内存池、连接池、对象池都具有以上的共同特点。

数据库连接池

什么是数据库连接池

创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。所以,在程序初始化的
时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,
还更加安全可靠。

为什么使用连接池

使用数据库连接池将使得数据库连接得到复用,避免重复的创建、释放连接等对于性能的开销;在初始化过程,连接池连接的初始化都已经完成,更有利于业务逻辑的进行,提高了系统响应速度;统一的申请与释放连接,避免了资源泄露。

不使用连接池

不使用连接池的逻辑:

  1. tcp三次握手建立连接
  2. MySQL认证的三次握手
  3. 真正的SQL执行
  4. MySQL的关闭
  5. TCP的四次握手关闭
    可以看到:网络IO较多 带宽利用率低 QPS较低 应用频繁低创建连接和关闭连接,导致临时对象较多,带来更多的内存碎片 ,在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)
    不使用连接池

使用连接池

在第一次访问建立连接,之后访问复用之前建立的连接,直接执行sql语句。优点:降低了网络开销;连接复用,降低了连接数;提升性能,降低开销;没有TIME_WAIT的问题。
使用连接池

数据库连接池运行机制

从连接池获取可用连接,使用完毕后归还给连接池,系统关闭前,断开所有连接并释放连接占用的系统资源。
1

连接池和线程池的关系

线程池:主动操作,主动获取任务并执行任务;
连接池:被动操作(类似内存池),池的队形被任务获取,执行完任务归还。
两者数量的关系:一般线程池数量和连接对象数量一致。
2

MySQL连接池

MySQL连接池设计逻辑

使用连接池需要预先建立数据库连接,请求获取连接流程图如下:
构造函数:
3

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;
	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; // 最小连接数量,目前是1
}

初始化:
4

int CDBConn::Init()
{
	m_mysql = mysql_init(NULL);	// mysql_标准的mysql c client对应的api
	if (!m_mysql)
	{
		log_error("mysql_init failed\n");
		return 1;
	}

	my_bool reconnect = true;
	mysql_options(m_mysql, MYSQL_OPT_RECONNECT, &reconnect);	// 配合mysql_ping实现自动重连
	mysql_options(m_mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");	// utf8mb4和utf8区别,设置成utf8mb4不会报错

	// ip 端口 用户名 密码 数据库名
	if (!mysql_real_connect(m_mysql, m_pDBPool->GetDBServerIP(), m_pDBPool->GetUsername(), m_pDBPool->GetPasswrod(),
							m_pDBPool->GetDBName(), m_pDBPool->GetDBServerPort(), NULL, 0))
	{
		log_error("mysql_real_connect failed: %s\n", mysql_error(m_mysql));
		return 2;
	}

	return 0;
}

请求获取连接:
5

/*
 *TODO: 增加保护机制,把分配的连接加入另一个队列,这样获取连接时,如果没有空闲连接,
 *TODO: 检查已经分配的连接多久没有返回,如果超过一定时间,则自动收回连接,放在用户忘了调用释放连接的接口
 * 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)		// 死等,直到有连接可以用 或者 连接池要退出
			{
				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 // 还没有到最大连接则创建连接
		{
			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++;
				// log_info("new db connection: %s, conn_cnt: %d\n", m_pool_name.c_str(), m_db_cur_conn_cnt);
			}
		}
	}

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

	return pConn;
}

归还连接:

6

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");
	}
}

析构线程池:
7

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();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
数据库连接池是一种用于管理数据库连接的技术,它可以在应用程序和数据库之间创建一组数据库连接,并且对这些连接进行有效的管理,以提高应用程序的性能和可伸缩性。 数据库连接池的主要作用是减少应用程序与数据库之间的连接数,避免了频繁创建和关闭连接所带来的性能开销和系统资源的浪费。同时,它还可以有效地管理连接的使用,防止因为连接泄露或长时间占用而导致的性能问题和系统故障。 实现原理数据库连接池实现原理主要包括以下几个步骤: 1.初始化连接池:在应用程序启动时,创建一定数量的数据库连接,并将这些连接存储在连接池中。 2.请求连接:当应用程序需要访问数据库时,从连接池中获取一个可用的连接。 3.使用连接:使用获取的连接进行数据库操作。 4.释放连接:使用完连接后,将连接返回到连接池中,以便其他应用程序可以使用它。 5.连接池管理:连接池需要对连接的状态进行管理,包括连接的可用性、使用次数、超时时间等,以保证连接的有效性和可靠性。 在实现连接池时,需要考虑以下几个方面: 1.连接池大小:连接池大小需要根据应用程序的并发访问量和数据库的负载情况来确定。 2.连接获取方式:连接可以采用懒加载(Lazy Load)的方式获取,也可以在初始化连接池时就创建所有的连接。 3.连接超时处理:连接在长时间不使用后可能会失效,连接池需要对这种情况进行处理,例如重新创建连接或关闭失效连接。 4.连接池性能监控:连接池需要对连接的使用情况进行监控,以便及时发现和处理性能问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值