为了避免频繁的创建、释放连接引起的性能开销,于是引入了连接池来得到资源的服用,能更快的系统响应,以及统一的连接管理,避免了数据库连接泄露。
数据库连接池设计大同小异,主要考虑几个问题:如果通过队列管理连接、如何获取连接、如何归还连接、如何处理扩容问题等。解决以上问题,基本就可以实现一个简单的连接池。
首先明确一点,就是连接池只是负责管理连接对象,而连接对象才是真正干活的部分。
(1)连接对象设计
这里的连接对象(文中为 CDBConnect)中包括连接建立、释放、数据库表的操作(增删改查)、事务(开启、提交、回滚)等方法的实现。
//初始化建立连接 int CDBConnect::Init(const char* db_server_ip, uint16_t db_server_port,const char* username, const char* password, const char* db_name) { m_MYSQL = mysql_init(NULL); // mysql_标准的mysql c client对应的api if (!m_mysql) { log_error("mysql_init failed\n"); return -1; } if (!db_server_ip) { log_error("db_server_ip is null\n"); return -1; } if (!username) { log_error("username is null\n"); return -1; } if (!password) { log_error("password is null\n"); return -1; } if (!db_name) { log_error("db_name is null\n"); return -1; } bool reconnect = true; mysql_options(m_mysql, MYSQL_OPT_RECONNECT, &reconnect); // 配合mysql_ping实现自动重连 mysql_options(m_mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4"); // utf8mb4和utf8区别 // ip、用户名、密码、数据库名、端口 if (!mysql_real_connect(m_mysql, db_server_ip, username, password, db_name, db_server_port, NULL, 0)) { log_error("mysql_real_connect failed: %s\n", mysql_error(m_mysql)); return -1; } return 0; }
C++后台开发架构师免费学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以自行添加点击 正在跳转 群文件共享
执行查询,首先对返回结果对象,单独经过封装,实现表中字段类型值的封装,便于获取值。
//结果集 CResultSet::CResultSet(MYSQL_RES *res) { m_res = res; // map table field key to index in the result array int num_fields = mysql_num_fields(m_res); MYSQL_FIELD *fields = mysql_fetch_fields(m_res); for (int i = 0; i < num_fields; i++) { // 多行 m_key_map.insert(make_pair(fields[i].name, i)); } } CResultSet::~CResultSet() { if (m_res) { mysql_free_result(m_res); m_res = NULL; } } bool CResultSet::Next() { m_row = mysql_fetch_row(m_res); if (m_row) { return true; } else { return false; } } int CResultSet::_GetIndex(const char *key) { map<string, int>::iterator it = m_key_map.find(key); if (it == m_key_map.end()) { return -1; } else { return it->second; } } int CResultSet::GetInt(const char *key) { int idx = _GetIndex(key); if (idx == -1) { return 0; } else { return atoi(m_row[idx]); // 有索引 } } char *CResultSet::GetString(const char *key) { int idx = _GetIndex(key); if (idx == -1) { return NULL; } else { return m_row[idx]; // 列 } }
查询实现如下:
//执行查询,在使用完结果集CResultSet后记得释放 CResultSet *CDBConnect::ExecuteQuery(const char *sql_query) { mysql_ping(m_mysql); if (mysql_real_query(m_mysql, sql_query, strlen(sql_query))) { log_error("mysql_real_query failed: %s, sql: %s\n", mysql_error(m_mysql), sql_query); return NULL; } // 返回结果 MYSQL_RES *res = mysql_store_result(m_mysql); // 返回结果 if (!res) { log_error("mysql_store_result failed: %s\n", mysql_error(m_mysql)); return NULL; } CResultSet *result_set = new CResultSet(res); // 存储到CResultSet return result_set; }
事务操作如下:
//开启事务 bool CDBConnect::Starttransaction() { mysql_ping(m_mysql); if (mysql_real_query(m_mysql, "start transaction\n", 17)) { log_error("mysql_real_query failed: %s, sql: start transaction\n", mysql_error(m_mysql)); return false; } return true; } //回滚 bool CDBConnect::Rollback() { mysql_ping(m_mysql); if (mysql_real_query(m_mysql, "rollback\n", 8)) { log_error("mysql_real_query failed: %s, sql: rollback\n", mysql_error(m_mysql)); return false; } return true; } //提交 bool CDBConnect::commit() { mysql_ping(m_mysql); if (mysql_real_query(m_mysql, "commit\n", 6)) { log_error("mysql_real_query failed: %s, sql: commit\n", mysql_error(m_mysql)); return false; } return true; }
(2)队列管理
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; // 最小连接数量 } // 释放连接池 CDBPool::~CDBPool() { std::lock_guard<std::mutex> lock(m_mutex); m_abort_request = true; m_cond_var.notify_all(); // 通知所有在等待的 //强制删除已用的连接 for (list<CDBConnect *>::iterator it = m_u(); it != m_u(); it++) { CDBConnect *pConn = *it; delete pConn; } m_u(); //删除空闲的连接 for (list<CDBConnect *>::iterator it = m_(); it != m_(); it++) { CDBConnect *pConn = *it; delete pConn; } m_(); } int CDBPool::Init() { // 创建固定最小的连接数量 for (int i = 0; i < m_db_cur_conn_cnt; i++) { CDBConnect *pDBConn = new CDBConnect(); int ret = pDBConn->Ini(),m_db_server_port,m_u(),m_(),m_db_name.c_str()); if (ret < 0) { delete pDBConn; return ret; } //添加到空闲的链表 m_(pDBConn); } return 0; } /* * timeout_ms默认为-1死等 * timeout_ms >=0 则为等待的时间 */ 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 ()) { // 第一步先检测 当前连接数量是否达到最大的连接数量 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] { // 当前连接数量小于最大连接数量 或者请求释放连接池时退出 return (!m_()) | 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] { return (!m_()) | m_abort_request; }); // 带超时功能时还要判断是否为空 if())// 如果连接池还是没有空闲则退出 { return NULL; } } if(m_abort_request) { log_warn("have aboort\n"); return NULL; } } else // 还没有到最大连接则创建连接,扩容 { CDBConnect *pDBConn = new CDBConnect(); //新建连接 int ret = pDBConn->Ini(),m_db_server_port,m_u(),m_(),m_db_name.c_str()); if (ret < 0) { log_error("Init DBConnecton failed\n\n"); delete pDBConn; return NULL; } m_(pDBConn); m_db_cur_conn_cnt++; log_info("new db connection: %s, conn_cnt: %d\n", m_(), m_db_cur_conn_cnt); } } CDBConnect *pConn = m_();// 获取连接 m_(); // 从空闲队列删除 // pConn->setCurrentTime(); // 设置连接使用的开始时间 m_u(pConn); return pConn; } void CDBPool::RelDBConn(CDBConnect *pConn) { std::lock_guard<std::mutex> lock(m_mutex); list<CDBConnect *>::iterator it = m_(); for (; it != m_(); it++) // 避免重复归还 { if (*it == pConn) { break; } } if (it == m_()) { m_u(pConn);//从已用的链表中删除 m_(pConn);//添加到空闲链表 m_cond_var.notify_one(); // 通知取队列 } else { log_error("RelDBConn failed\n"); } } // 遍历检测是否超时未归还 // pConn->isTimeout(); // 当前时间 - 被请求的时间 // 强制回收 从m_used_list 放回 m_free_list
以上代码中的初始化、释放都比较好理解;对于从连接池获取一个连接,考虑到扩容处理,当空闲链表为空时,需要扩容,先判断当前连接数量是否已经大于连接数量上限,如果不大于,则新建一个连接,并添加到空闲链表中,如果大于,则进行超时处理;如果空闲链表不为空,则从空闲链表头部,取出一个连接,此处使用了另外一个链表m_used_list,保存已使用的连接,便于做连接使用超时处理。连接使用完后,进行归还操作,首先在空闲链表中遍历归还的连接是否存在,避免重复归还,如果在空闲链表中找不到,则把连接重新添加回空闲链表,并从已使用链表中删除该连接。
对于扩展进行连接超时处理,单独开启一个线程检测链表m_used_list中连接使用时间,如果超时,则强制归还,从m_used_list 放回 m_free_list中。
以上是MySql的连接池,如果是Redis,连接池的管理不变,主要变化就是连接对象的实现(包括常用指令对应的实现方法:get、set、mget、incr、decr、hget、hset、hmset、hmget、lpush、rpush、lrange.....等),使用开源hiredis进行封装,hiredis提供了C语言版本访问redis的常用操作,并且支持一次发送多条指令,所有结果一次返回(异步请求方式),不像MySQL单个连接执行一条指令,必须等待响应返回后,才能执行下一条指令(同步请求方式)。
原文地址:数据库连接池 - MrJuJu - 博客园