今天闲着没事干,手搓一下C++ redis连接池。不要问我为什么手搓,因为官方没给!又是羡慕Java的一天。
Java的设计理念:轻轻打下import,剩下的交给jar包
C++的设计理念:道法自然 (给你铁与火,你造不出剑别赖我)
Redis连接池的作用:管理个连接,避免反复创建和销毁带来的开销,还能提高并发量。
一个Redis连接池的基础部分包括定义连接池、连接池初始化、获取连接、回收连接、销毁连接池。同时还要兼顾连接池的线程安全。
本文实现的连接池基于hiredis构建,下载地址:Releases · redis/hiredis · GitHub
连接池头文件定义:
#include <condition_variable>
#include <iostream>
#include <list>
#include <map>
#include <mutex>
#include <vector>
#include "hiredis.h"
using std::list;
using std::map;
using std::string;
using std::vector;
// 这里泛指一个redis连接,这个连接应当包括连接数据库插入删除等操作
class CacheConn{}
class CachePool {
public:
// 连接池属性
CachePool(const char *pool_name, const char *server_ip, int server_port,
int db_index, const char *password, int max_conn_cnt);
// 析构函数
virtual ~CachePool();
// 初始化方法
int Init();
// 获取空闲的连接资源
CacheConn *GetCacheConn(const int timeout_ms = 0);
// Pool回收连接资源
void RelCacheConn(CacheConn *cache_conn);
const char *GetPoolName() { return pool_name_.c_str(); }
const char *GetServerIP() { return server_ip_.c_str(); }
const char *GetPassword() { return password_.c_str(); }
int GetServerPort() { return m_server_port; }
int GetDBIndex() { return db_index_; }
private:
// 连接池名称
string pool_name_;
// 数据库IP地址
string server_ip_;
// 数据库密码
string password_;
// 端口号
int m_server_port;
// redis db index
int db_index_;
// 当前连接的数量
int cur_conn_cnt_;
// 最大连接的数量
int max_conn_cnt_;
// 空闲连接列表
list<CacheConn *> free_list_;
// 互斥锁
std::mutex m_mutex;
// 条件变量
std::condition_variable cond_var_;
// 中断请求标识
bool abort_request_ = false;
};
源代码:
连接池的定义、初始化和销毁
CachePool::CachePool(const char *pool_name, const char *server_ip,
int server_port, int db_index, const char *password,
int max_conn_cnt) {
pool_name_ = pool_name;
server_ip_ = server_ip;
m_server_port = server_port;
db_index_ = db_index;
password_ = password;
max_conn_cnt_ = max_conn_cnt;
cur_conn_cnt_ = MIN_CACHE_CONN_CNT;
}
CachePool::~CachePool() {
{
// 获取互斥锁
std::lock_guard<std::mutex> lock(m_mutex);
// 设置中断请求位true
abort_request_ = true;
// 通知所有在等待的线程,线程池已经丸啦,该自尽了
cond_var_.notify_all();
}
{
// 释放所有连接
std::lock_guard<std::mutex> lock(m_mutex);
for (list<CacheConn *>::iterator it = free_list_.begin();
it != free_list_.end(); it++) {
CacheConn *pConn = *it;
delete pConn;
}
}
// 清空连接池
free_list_.clear();
cur_conn_cnt_ = 0;
}
int CachePool::Init() {
for (int i = 0; i < cur_conn_cnt_; i++) {
// 创建一个连接
CacheConn *pConn =
new CacheConn(server_ip_.c_str(), m_server_port, db_index_,
password_.c_str(), pool_name_.c_str());
// 初始化失败就删除,返回-1
if (pConn->Init()) {
delete pConn;
return 1;
}
创建成功后存储进入free_list_
free_list_.push_back(pConn);
}
log_info("cache pool: %s, list size: %lu\n", pool_name_.c_str(),
free_list_.size());
return 0;
}
从连接池中获取一个redis连接
CacheConn *CachePool::GetCacheConn(const int timeout_ms) {
// 获取独占锁
std::unique_lock<std::mutex> lock(m_mutex);
// 收到终止标识直接退出
if (abort_request_) {
log_info("have aboort\n");
return NULL;
}
// 2 队列中没有任何可用连接
if (free_list_.empty())
{
// 第一步先检测 当前连接数量是否达到最大的连接数量
if (cur_conn_cnt_ >= max_conn_cnt_) // 等待的逻辑
{
// 如果已经到达了,看看是否需要超时等待
if (timeout_ms <= 0) // 死等,直到有连接可以用 或者 连接池要退出
{
log_info("wait ms:%d\n", timeout_ms);
cond_var_.wait(lock, [this] {
// 当前连接数量小于最大连接数量 或者请求释放连接池时退出
return (!free_list_.empty()) | abort_request_;
});
} else {
// return如果返回 false,继续wait(或者超时),
// 如果返回true退出wait 1.m_free_list不为空 2.超时退出
// 3. m_abort_request被置为true,要释放整个连接池
cond_var_.wait_for(
lock, std::chrono::milliseconds(timeout_ms),
[this] { return (!free_list_.empty()) | abort_request_; });
// 带超时功能时还要判断是否为空
if (free_list_.empty()) // 如果连接池还是没有空闲则退出
{
return NULL;
}
}
if (abort_request_) {
log_warn("have aboort\n");
return NULL;
}
} else // 还没有到最大连接则创建连接
{
CacheConn *db_conn =
new CacheConn(server_ip_.c_str(), m_server_port, db_index_,
password_.c_str(), pool_name_.c_str()); //新建连接
int ret = db_conn->Init();
if (ret) {
log_error("Init DBConnecton failed\n\n");
delete db_conn;
return NULL;
} else {
free_list_.push_back(db_conn);
cur_conn_cnt_++;
// log_info("new db connection: %s, conn_cnt: %d\n",
// m_pool_name.c_str(), m_cur_conn_cnt);
}
}
}
// 队列中有可用连接直接取出第一个连接然后返回
CacheConn *pConn = free_list_.front();
free_list_.pop_front();
return pConn;
}
归还一个连接:
void CachePool::RelCacheConn(CacheConn *p_cache_conn) {
std::lock_guard<std::mutex> lock(m_mutex);
// 遍历连接池看看是不是已经存在
list<CacheConn *>::iterator it = free_list_.begin();
for (; it != free_list_.end(); it++) {
if (*it == p_cache_conn) {
break;
}
}
// 不存在就放进去,存在说明某个地方逻辑写错了,把错误信息写到日志里面
if (it == free_list_.end()) {
// m_used_list.remove(pConn);
free_list_.push_back(p_cache_conn);
cond_var_.notify_one(); // 通知取队列
} else {
log_error("RelDBConn failed\n"); // 不再次回收连接
}
}
好累啊,晚上去吃华莱士奖励一下自己,这是一条吃饭博客,由挨踢零声赞助。