池化技术
池化技术能够减少资源对象的创建次数,提高程序的响应性能,特别是在高并发下这种提高更加明显。
使用池化技术缓存的资源对象有如下共同特点:
1. 对象创建时间长;
2. 对象创建需要大量资源;
3. 对象创建后可被重复使用
像常见的线程池、内存池、连接池、对象池都具有以上的共同特点。
数据库连接池
什么是数据库连接池
创建数据库连接是一个很耗时的操作,也容易对数据库造成安全隐患。所以,在程序初始化的
时候,集中创建多个数据库连接,并把他们集中管理,供程序使用,可以保证较快的数据库读写速度,
还更加安全可靠。
为什么使用连接池
使用数据库连接池将使得数据库连接得到复用,避免重复的创建、释放连接等对于性能的开销;在初始化过程,连接池连接的初始化都已经完成,更有利于业务逻辑的进行,提高了系统响应速度;统一的申请与释放连接,避免了资源泄露。
不使用连接池
不使用连接池的逻辑:
- tcp三次握手建立连接
- MySQL认证的三次握手
- 真正的SQL执行
- MySQL的关闭
- TCP的四次握手关闭
可以看到:网络IO较多 带宽利用率低 QPS较低 应用频繁低创建连接和关闭连接,导致临时对象较多,带来更多的内存碎片 ,在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)
使用连接池
在第一次访问建立连接,之后访问复用之前建立的连接,直接执行sql语句。优点:降低了网络开销;连接复用,降低了连接数;提升性能,降低开销;没有TIME_WAIT的问题。
数据库连接池运行机制
从连接池获取可用连接,使用完毕后归还给连接池,系统关闭前,断开所有连接并释放连接占用的系统资源。
连接池和线程池的关系
线程池:主动操作,主动获取任务并执行任务;
连接池:被动操作(类似内存池),池的队形被任务获取,执行完任务归还。
两者数量的关系:一般线程池数量和连接对象数量一致。
MySQL连接池
MySQL连接池设计逻辑
使用连接池需要预先建立数据库连接,请求获取连接流程图如下:
构造函数:
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
}
初始化:
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;
}
请求获取连接:
/*
*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;
}
归还连接:
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");
}
}
析构线程池:
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();
}