前言
记得鲁迅说过,天下武功,维快不破!
但记得鲁迅还说过,男人不能轻易说自己很快!
是不是经常听到我们的系统是ns级别的响应,夸张了,哦不对是毫秒。
是不是经常遇到这种池那种池,为啥要用他们?说白了,就是为了快!
池化技术
池化技术减少了各种对象的创建和销毁的开销,间接提高了服务响应的速度。
其实我们常见的内存池/连接池/线程池,基本都是为了提高速度来的。
这是这些池化技术的共同特点:
1. 减少了对象创建时间
2. 减少对象创建消耗大量的资源
3. 对象创建后可被重复使⽤
数据库连接池技术
我们都知道,数据库的连接数量是有限制,创建和释放的开销并不小,如果我们在程序启动时,就进行数据库的连接的创建,后面如果需要使用到的地方,直接从连接池中获取使用,这样,我们的连接不但复用了,也减少了频繁操作数据库的连接导致的一些其他问题。
如果假设你不使用连接池,每个使用到数据库的地方,都取创建连接,发送数据,关闭连接,这样存在的以下三个问题:
1、连接和释放需要时间,三次握手四次挥手,加上中间的时间消耗,系统性能并不好
2、关闭连接在服务端会导致出现Time_Wait状态的TCP链接
3、管理维护混乱,依赖程序员的水平,不能做到组件化。
数据库连接池实现
连接池需要的数据结构,我们知道,至少要拥有一个连接池管理器,
对外的接口:
1、初始化池:包括对数据库的连接,连接数管理
2、获取连接:外部可通过池获取连接
3、归还连接:外部使用完连接后要归还
连接池内部应该实现对连接信息的管理。
以下提供了一种大概的实现方式:
class CDBPool {
public:
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, LOGHANDLE pLogHandle);
virtual ~CDBPool();
int Init(); // 连接数据库,创建连接
CDBConnect* GetDBConn(const int timeout_ms = -1); // 获取连接资源
void RelDBConn(CDBConnect* pConn); // 归还连接资源
const char* GetPoolName() { return m_pool_name.c_str(); }
const char* GetDBServerIP() { return m_db_server_ip.c_str(); }
uint16_t GetDBServerPort() { return m_db_server_port; }
const char* GetUsername() { return m_username.c_str(); }
const char* GetPasswrod() { return m_password.c_str(); }
const char* GetDBName() { return m_db_name.c_str(); }
private:
string m_pool_name; // 连接池名称
string m_db_server_ip; // 数据库ip
uint16_t m_db_server_port; // 数据库端口
string m_username; // 用户名
string m_password; // 用户密码
string m_db_name; // db名称
int m_db_cur_conn_cnt; // 当前启用的连接数量
int m_db_max_conn_cnt; // 最大连接数量
list<CDBConnect*> m_free_list; // 空闲的连接
list<CDBConnect*> m_used_list; // 记录已经被请求的连接
std::mutex m_mutex;
std::condition_variable m_cond_var;
bool m_abort_request = false;
LOGHANDLE m_pLog;
// CThreadNotify m_free_notify; // 信号量
};
其中维持了两个队列,一个空闲的队列还有一个请求的连接队列。
获取连接的实现:
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 (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 // 还没有到最大连接则创建连接
{
CDBConnect *pDBConn = new CDBConnect(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);
}
}
}
CDBConnect *pConn = m_free_list.front(); // 获取连接
m_free_list.pop_front(); // STL 吐出连接,从空闲队列删除
// pConn->setCurrentTime(); // 伪代码
m_used_list.push_back(pConn); //
return pConn;
}
归还连接的实现:
void CDBPool::RelDBConn(CDBConnect *pConn)
{
std::lock_guard<std::mutex> lock(m_mutex);
list<CDBConnect *>::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");
}
}
连接对象应该如何实现
应该对外要实现的接口:
1、增删改查。
2、事务操作,启动,回滚等操作。
对内的实现:与真正的MYSQL对象进行交互。
实现的代码:
class CDBConnect {
public:
CDBConnect(CDBPool* pDBPool);
virtual ~CDBConnect();
int Init();
// 创建表
bool ExecuteCreate(const char* sql_query);
// 删除表
bool ExecuteDrop(const char* sql_query);
// 查询
CResultSet* ExecuteQuery(const char* sql_query);
/**
* 执行DB更新,修改
*
* @param sql_query sql
* @param care_affected_rows 是否在意影响的行数,false:不在意;true:在意
*
* @return 成功返回true 失败返回false
*/
bool ExecuteUpdate(const char* sql_query, bool care_affected_rows = true);
uint32_t GetInsertId();
// 开启事务
bool StartTransaction();
// 提交事务
bool Commit();
// 回滚事务
bool Rollback();
// 获取连接池名
const char* GetPoolName();
MYSQL* GetMysql() { return m_mysql; }
private:
CDBPool* m_pDBPool; // to get MySQL server information
MYSQL* m_mysql; // 对应一个连接
char m_escape_string[MAX_ESCAPE_STRING_LEN + 1];
};
因为都是对mysql对象的操作,我就简单挑一个接口的实现来举例
CResultSet* CDBConnect::ExecuteQuery(const char* sql_query)
{
mysql_ping(m_pmySql);
if (mysql_real_query(m_pmySql, sql_query, strlen(sql_query)))
{
LogError(m_pLog,"mysql_real_query failed: %s, sql: %s\n", mysql_error(m_pmySql), sql_query);
return NULL;
}
// 返回结果
MYSQL_RES* res = mysql_store_result(m_pmySql); // 返回结果
if (!res)
{
LogError(m_pLog,"mysql_store_result failed: %s\n", mysql_error(m_pmySql));
return NULL;
}
CResultSet* result_set = new CResultSet(res); // 存储到CResultSet
return result_set;
}
其中,这是由返回结果的,我们通常要对mysql的返回结果转换为我们自己想要的数据结构如下
// 返回结果 select的时候用
class CResultSet {
public:
CResultSet(MYSQL_RES* res);
virtual ~CResultSet();
bool Next();
int GetInt(const char* key);
char* GetString(const char* key);
private:
int _GetIndex(const char* key);
MYSQL_RES* m_res;
MYSQL_ROW m_row;
map<string, int> m_key_map;
};
这样,应用层获取到的结构就可以直接操作。