连接池的设计实现
一个资源满足以下两个条件,就可以考虑使用池化技术
- 创建开销大
- 可重复使用
如线程池、内存池、mysql与oracle连接池
连接池
连接池往往和线程池搭配使用,连接池一般采用链表存储(是不是有点类似线程池中的消息队列)
连接池中的连接需要根据线程数量设置
为什么需要连接池:比如注册个账号,需要以下过程
获取连接 =》 执行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认证操作,性能提升了一倍左右)
- 连接池的构造与析构函数:
// 构造赋值
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();
}
- 初始化:
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;
}
- 请求获取连接:
/*
* 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;
}
- 归还连接:
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");
}
}