1. 基础使用
1.1 核心数据结构
- redisContext: 保存连接状态(Socket、缓冲区、错误信息等)。每个线程应拥有独立的 Context。
- redisReply: 保存命令执行后的返回结果。使用后必须手动释放。
1.2 核心 api
1.2.1 redisConnectWithTimeout
cpp
复制代码
redisContext *redisConnectWithTimeout(const char *ip, int port, struct timeval timeout);
| 项目 |
说明 |
| 功能 |
与 Redis 服务器建立 TCP 连接(带超时控制) |
| 参数 |
ip: 服务器 IP 地址 port: 服务器端口号 timeout: 连接超时结构体(NULL 使用默认) |
| 返回 |
成功:redisContext* 指针 失败:NULL 或 context->err != 0 |
| 内存 |
调用者需负责调用 redisFree() 释放 |
cpp
复制代码
// 示例
struct timeval timeout = { 1, 500000 }; // 1.5 秒
redisContext *c = redisConnectWithTimeout("127.0.0.1", 6379, timeout);
1.2.2 redisConnect
cpp
复制代码
redisContext *redisConnect(const char *ip, int port);
| 项目 |
说明 |
| 功能 |
与 Redis 服务器建立 TCP 连接(使用默认超时) |
| 参数 |
ip: 服务器 IP 地址 port: 服务器端口号 |
| 返回 |
同 redisConnectWithTimeout |
| 区别 |
不指定超时时间,使用库默认值 |
1.2.3 redisFree
cpp
复制代码
void redisFree(redisContext *c);
| 项目 |
说明 |
| 功能 |
关闭连接并释放 redisContext 内存 |
| 参数 |
c: redisContext 指针 |
| 返回 |
无 |
| 注意 |
必须调用,否则内存泄漏 |
cpp
复制代码
// 示例
if (c) {
redisFree(c); // 程序退出前必须调用
c = NULL; // 避免悬空指针
}
1.2.4 redisSetTimeout
cpp
复制代码
int redisSetTimeout(redisContext *c, struct timeval tv);
| 项目 |
说明 |
| 功能 |
设置连接的读写超时时间 |
| 参数 |
c: redisContext 指针 tv: 超时结构体 |
| 返回 |
REDIS_OK (0) 成功,REDIS_ERR (-1) 失败 |
| 场景 |
连接建立后动态调整超时 |
cpp
复制代码
// 示例
struct timeval tv = { 3, 0 }; // 3 秒
redisSetTimeout(c, tv);
1.2.5 redisCommand
cpp
复制代码
void *redisCommand(redisContext *c, const char *format, ...);
| 项目 |
说明 |
| 功能 |
同步执行 Redis 命令(printf 风格格式化) |
| 参数 |
c: redisContext 指针 format: 命令格式字符串 ...: 可变参数 |
| 返回 |
成功:redisReply* 指针 失败:NULL |
| 注意 |
返回值必须用 freeReplyObject() 释放 |
cpp
复制代码
// 示例
redisReply *reply = (redisReply*)redisCommand(c, "SET %s %s", "key", "value");
if (reply) {
// 处理响应
freeReplyObject(reply);
}
1.2.6 freeReplyObject
cpp
复制代码
void freeReplyObject(void *reply);
| 项目 |
说明 |
| 功能 |
释放 redisReply 对象及其内部内存 |
| 参数 |
reply: redisReply* 指针 |
| 返回 |
无 |
| 注意 |
每次 redisCommand 后必须调用 |
cpp
复制代码
// 示例
redisReply *reply = (redisReply*)redisCommand(c, "GET key");
if (reply) {
// 使用 reply->str 等
freeReplyObject(reply); // ⚠️ 必须释放
reply = NULL;
}
1.3 基础使用流程
标准流程:连接 -> 执行命令 -> 检查回复 -> 释放回复 -> 断开连接
cpp
复制代码
/**
* @file redis_hiredis_example.cpp
* @brief 使用 hiredis 库连接 Redis 并进行 SET/GET 操作的示例
*
* hiredis 是 Redis 官方的 C 语言客户端库,具有轻量级、高性能的特点。
* 在 C++ 项目中使用时,需注意内存管理(malloc/free 风格)。
*
* 编译示例 (Linux):
* g++ -o redis_example redis_hiredis_example.cpp -lhiredis
*/
#include <iostream>
#include <hiredis/hiredis.h>
int main() {
// =========================================================================
// 1. 建立连接
// =========================================================================
// 设置连接超时时间为 1.5 秒 (1 秒 + 500,000 微秒)
struct timeval timeout = { 1, 500000 };
/**
* @函数:redisConnectWithTimeout
* @说明:尝试与 Redis 服务器建立 TCP 连接。
* @参数:
* - ip: Redis 服务器 IP 地址 (如 "127.0.0.1")
* - port: Redis 端口号 (默认 6379)
* - timeout: 连接超时结构体,若为 NULL 则使用默认超时
* @返回:
* - 成功:返回 redisContext 指针
* - 失败:返回 NULL 或 redisContext->err 被置位
* @注意:redisContext 保存了连接的状态信息,后续所有操作都需传入此指针。
*/
redisContext* c = redisConnectWithTimeout("127.0.0.1", 6379, timeout);
// 检查连接是否成功
// c == NULL 表示内存分配失败或连接完全无法建立
// c->err 非 0 表示连接过程中发生错误 (如拒绝连接、超时等)
if (c == NULL || c->err) {
if (c) {
// 如果 c 不为 NULL 但有 err,打印具体错误信息
// errstr 是人类可读的错误描述字符串
std::cerr << "Connection error: " << c->errstr << std::endl;
/**
* @函数:redisFree
* @说明:释放 redisContext 结构体并关闭连接。
* @注意:无论连接是否成功,只要分配了 context,退出前都必须调用此函数防止内存泄漏。
*/
redisFree(c);
} else {
// c 为 NULL,说明连 context 结构体都没分配成功
std::cerr << "Connection error: can't allocate redis context" << std::endl;
}
return 1;
}
// =========================================================================
// 2. 执行 SET 命令
// =========================================================================
/**
* @函数:redisCommand
* @说明:同步执行 Redis 命令。支持 printf 风格的格式化字符串。
* @参数:
* - c: redisContext 指针
* - format: 命令格式字符串 (如 "SET %s %s")
* - ...: 可变参数,填充格式字符串
* @返回:
* - 成功:返回 redisReply 指针 (包含服务器响应)
* - 失败:返回 NULL
* @注意:
* 1. 返回的 redisReply 对象必须由调用者手动释放。
* 2. 该函数是阻塞的,直到收到响应或超时。
*/
// 执行 SET name hiredis
// 注意:hiredis 会自动处理命令的序列化,不需要手动拼接字符串
redisReply* reply = (redisReply*)redisCommand(c, "SET %s %s", "name", "hiredis");
if (reply == NULL) {
// 命令执行失败 (可能是连接断开、超时等)
std::cerr << "Command failed" << std::endl;
redisFree(c);
return 1;
}
// 打印 SET 命令的返回结果
// 对于 SET 命令,成功通常返回 "OK"
// reply->str 指向响应字符串内容 (非 null 终止,需配合 len 使用,但打印时通常可直接用)
// reply->len 是字符串长度
std::cout << "SET Result: " << reply->str << std::endl;
/**
* @函数:freeReplyObject
* @说明:释放 redisCommand 返回的 redisReply 对象。
* @注意:
* 1. 每次调用 redisCommand 后,必须对应调用一次 freeReplyObject。
* 2. 该函数会递归释放 reply 内部的所有内存 (包括嵌套数组等)。
* 3. 如果不释放,会导致严重的内存泄漏。
*/
freeReplyObject(reply); // 3. 释放回复对象
// =========================================================================
// 3. 执行 GET 命令
// =========================================================================
// 执行 GET name
reply = (redisReply*)redisCommand(c, "GET %s", "name");
// 生产环境中,此处应先检查 reply 是否为 NULL
// if (reply == NULL) { 处理错误... }
/**
* @结构:redisReply
* @成员:
* - type: 响应类型 (REDIS_REPLY_STRING, REDIS_REPLY_ARRAY, REDIS_REPLY_INTEGER, etc.)
* - str: 字符串内容 (当 type 为 STRING 或 STATUS 时有效)
* - len: 字符串长度
* - integer: 整数值 (当 type 为 INTEGER 时有效)
* - elements: 数组元素个数 (当 type 为 ARRAY 时有效)
* - element: 数组元素指针数组
*/
if (reply->type == REDIS_REPLY_STRING) {
// 检查返回类型是否为字符串
// Redis 协议中,GET 成功返回字符串类型
std::cout << "GET Result: " << reply->str << std::endl;
} else if (reply->type == REDIS_REPLY_NIL) {
// 处理键不存在的情况 (Redis 返回 nil)
std::cout << "Key 'name' does not exist." << std::endl;
}
freeReplyObject(reply); // 释放 GET 命令的回复对象
// =========================================================================
// 4. 断开连接
// =========================================================================
// 程序结束前,关闭连接并释放 context 内存
redisFree(c);
return 0;
}
cpp
复制代码
┌─────────────────────────────────────────────────────────────┐
│ hiredis 完整生命周期 │
└─────────────────────────────────────────────────────────────┘
┌──────────────┐
│ 1. 创建连接 │
│ redisConnect │
└──────┬───────┘
│
▼
┌──────────────┐ ┌──────────────┐
│ 检查连接状态 │───▶│ 连接失败 │
│ c->err == 0 │ 否 │ redisFree │
└──────┬───────┘ └──────────────┘
│ 是
▼
┌──────────────┐
│ 2. 执行命令 │
│ redisCommand │
└──────┬───────┘
│
▼
┌──────────────┐ ┌──────────────┐
│ 检查 reply │───▶│ reply == NULL │
│ │ 是 │ 处理错误 │
└──────┬───────┘ └──────────────┘
│ 否
▼
┌──────────────┐
│ 3. 处理响应 │
│ 检查 type │
└──────┬───────┘
│
▼
┌──────────────┐
│ 4. 释放响应 │
│ freeReplyObj │
└──────┬───────┘
│
▼
┌──────────────┐
│ 5. 关闭连接 │
│ redisFree │
└──────────────┘
1.4 C++ 封装
redis_connection.h
cpp
复制代码
/**
* @file redis_connection.h
* @brief Redis 连接封装模块头文件
* @author DFS Team
* @date 2025-02-16
*
* @version 1.0.0
* - 初始版本,实现 Redis 连接封装
*
* 功能说明:
* - 封装 Redis 连接的创建、认证和关闭 (RAII 风格)
* - 提供同步命令执行接口 (支持格式化字符串和 argv 数组)
* - 支持连接超时和读写超时配置
* - 线程安全操作 (通过互斥锁保护 redisContext)
*
* 注意事项:
* - redisContext 本身不是线程安全的,本类通过 mutex_ 保证同一时刻只有一个线程使用连接
* - executeCommand 返回的 redisReply* 必须由调用者手动释放 (freeReplyObject)
*/
#ifndef DFS_REDIS_REDIS_CONNECTION_H
#define DFS_REDIS_REDIS_CONNECTION_H
#include <string>
#include <memory>
#include <mutex>
#include <chrono>
#include <ctime>
#include <hiredis/hiredis.h>
namespace dfs {
namespace redis {
/**
* @brief Redis 连接配置结构体
*
* 包含 Redis 连接所需的所有配置参数,用于初始化 RedisConnection
*/
struct RedisConfig {
std::string host = "127.0.0.1"; ///< Redis 服务器地址 (IP 或域名)
int port = 6379; ///< Redis 端口号
std::string password; ///< 认证密码 (为空则不认证)
int database = 0; ///< 数据库编号 (0-15)
int connectionTimeout = 5000; ///< 连接超时时间 (毫秒)
int socketTimeout = 3000; ///< 读写超时时间 (毫秒),0 表示无限等待
};
/**
* @brief Redis 连接类
*
* 封装单个 Redis 连接生命周期管理。
* 此类不是连接池,每个实例对应一个 TCP 连接。
* 多线程共享此实例时,内部互斥锁会串行化命令执行。
*/
class RedisConnection {
public:
// 使用 shared_ptr 管理 RedisConnection 生命周期,便于资源自动释放
using Ptr = std::shared_ptr<RedisConnection>;
// 禁用拷贝构造和拷贝赋值,防止多个对象管理同一个 redisContext 导致重复释放
RedisConnection(const RedisConnection&) = delete;
RedisConnection& operator=(const RedisConnection&) = delete;
// 1. 构造函数:仅初始化配置,不立即建立网络连接
explicit RedisConnection(const RedisConfig& config);
// 2. 析构函数:自动调用 close() 关闭连接,释放 redisContext 资源
~RedisConnection();
// 3. 建立 Redis 物理连接(线程安全),true 连接成功,false 连接失败
bool connect();
// 4. 检查连接是否有效(线程安全),true 连接有效,false 连接无效
bool isConnected() const;
// 5. 关闭 Redis 连接(线程安全)
void close();
/**
* @brief 6. 执行 Redis 命令(printf 格式化方式)
* @param format 命令格式字符串 (例如 "SET %s %s")
* @param ... 可变参数,用于填充格式字符串
* @return redisReply* 命令响应对象
* @return nullptr 执行失败或连接断开
* @warning 返回的 redisReply* 必须由调用者调用 freeReplyObject() 释放,否则内存泄漏
* @note 线程安全
*/
redisReply* executeCommand(const char* format, ...);
/**
* @brief 7. 执行 Redis 命令(argv 数组方式)
* @param argc 参数个数
* @param argv 参数内容数组
* @param argvlen 参数长度数组 (支持二进制安全)
* @return redisReply* 命令响应对象
* @return nullptr 执行失败或连接断开
* @warning 返回的 redisReply* 必须由调用者调用 freeReplyObject() 释放
* @note 线程安全。相比格式化方式,此方式更适合包含特殊字符或二进制数据的命令
*/
redisReply* executeCommandArgv(int argc, const char** argv, const size_t* argvlen);
/**
* @brief 8. 设置最后使用时间
* @param time 时间戳 (std::time_t)
* @note 用于连接池判断连接是否空闲超时
*/
void setLastUsedTime(std::time_t time);
// 9. 获取最后使用时间
std::time_t getLastUsedTime() const;
private:
// 执行密码认证,true 认证成功或无需认证,false 失败
bool authenticate();
// 选择数据库 (SELECT 命令),Cluster 只支持 DB 0,SELECT 会报错, true 选择成功或默认,false 选择失败
bool selectDatabase();
RedisConfig config_; // 连接配置信息
redisContext* context_ = nullptr; // hiredis 连接上下文指针 (核心资源)
std::time_t lastUsedTime_ = 0; // 最后使用时间戳
mutable std::mutex mutex_; // 互斥锁,保护 context_ 及命令执行的线程安全
};
} // namespace redis
} // namespace dfs
#endif // DFS_REDIS_REDIS_CONNECTION_H
redis_connection.cpp
cpp
复制代码
/**
* @file redis_connection.cpp
* @brief Redis 连接封装实现文件
* @author DFS Team
* @date 2025-02-16
*
* 实现细节:
* - 使用 hiredis 同步 API
* - 所有公共接口均加锁保证线程安全
* - 严格检查 redisContext 错误标志
*/
#include "redis_connection.h"
#include "../common/logger.h" // 设项目中存在日志模块
namespace dfs {
namespace redis {
// 构造函数:初始化配置和最后使用时间
RedisConnection::RedisConnection(const RedisConfig& config) : config_(config), lastUsedTime_(std::time(nullptr)) {}
// 析构函数:确保连接被关闭,防止资源泄漏
RedisConnection::~RedisConnection() { close(); }
bool RedisConnection::connect() {
// 加锁保护连接建立过程,防止多线程同时操作 context_
std::lock_guard<std::mutex> lock(mutex_);
// 如果已经连接,直接返回成功
if (context_) {
return true;
}
// 1. 发起连接
struct timeval timeout;
timeout.tv_sec = config_.connectionTimeout / 1000;
timeout.tv_usec = (config_.connectionTimeout % 1000) * 1000;
// 发起 TCP 连接请求
context_ = redisConnectWithTimeout(config_.host.c_str(), config_.port, timeout);
// 检查连接结果:context_ 为空表示分配失败,context_->err 非 0 表示连接错误
if (!context_ || context_->err) {
LOG_ERROR("Redis connect failed: %s", context_ ? context_->errstr : "unknown error");
if (context_) {
// 释放失败的 context,避免内存泄漏
redisFree(context_);
context_ = nullptr;
}
return false;
}
// 2. 设置读写超时 (socket timeout)
if (config_.socketTimeout > 0) {
struct timeval svTimeout;
svTimeout.tv_sec = config_.socketTimeout / 1000;
svTimeout.tv_usec = (config_.socketTimeout % 1000) * 1000;
/**
* @函数:redisSetTimeout
* @说明:设置当前连接的读写超时时间。
* @注意:这与连接超时不同,它控制 read/write 系统调用的阻塞时间。
*/
if (redisSetTimeout(context_, svTimeout) != REDIS_OK) {
LOG_WARN("Redis set socket timeout failed");
}
}
// 3. 执行认证 (如果配置了密码)
if (!authenticate()) {
close(); // 认证失败则关闭连接
return false;
}
// 4. 选择数据库 (如果非默认库 0)
if (!selectDatabase()) {
close(); // 选库失败则关闭连接
return false;
}
LOG_INFO("Redis connected to %s:%d", config_.host.c_str(), config_.port);
return true;
}
bool RedisConnection::isConnected() const {
return context_ != nullptr && context_->err == 0;
}
void RedisConnection::close() {
std::lock_guard<std::mutex> lock(mutex_);
if (context_) {
redisFree(context_);
context_ = nullptr;
}
}
bool RedisConnection::authenticate() {
// 如果没有配置密码,直接成功
if (config_.password.empty()) {
return true;
}
/**
* @函数:redisCommand
* @说明:发送 AUTH 命令。
* @注意:此处直接使用 redisCommand 因为是在 connect 内部调用,已持有锁。
*/
redisReply* reply = static_cast<redisReply*>(redisCommand(context_, "AUTH %s", config_.password.c_str()));
if (!reply) {
// 命令发送失败 (可能是连接突然断开)
LOG_ERROR("Redis AUTH failed: no reply");
return false;
}
// 检查响应类型:如果是 ERROR 类型,则认证失败
bool success = (reply->type != REDIS_REPLY_ERROR);
if (!success) {
// reply->str 包含错误信息,例如 "ERR invalid password"
LOG_ERROR("Redis AUTH failed: %s", reply->str);
}
// 释放响应对象
freeReplyObject(reply);
return success;
}
bool RedisConnection::selectDatabase() {
// 默认数据库为 0,无需发送 SELECT 命令
if (config_.database == 0) {
return true;
}
redisReply* reply = static_cast<redisReply*>(
redisCommand(context_, "SELECT %d", config_.database));
if (!reply) {
LOG_ERROR("Redis SELECT failed: no reply");
return false;
}
bool success = (reply->type != REDIS_REPLY_ERROR);
if (!success) {
LOG_ERROR("Redis SELECT failed: %s", reply->str);
}
freeReplyObject(reply);
return success;
}
redisReply* RedisConnection::executeCommand(const char* format, ...) {
// 加锁:保证同一时刻只有一个线程使用该 context 发送命令
std::lock_guard<std::mutex> lock(mutex_);
// 前置检查:连接是否有效
if (!context_ || context_->err) {
return nullptr;
}
// 处理可变参数
va_list ap;
va_start(ap, format);
/**
* @函数:redisvCommand
* @说明:与 redisCommand 类似,但接受 va_list 参数。
* @用途:用于封装可变参数函数 (如本类的 executeCommand)。
* @返回:redisReply* 需调用者释放
*/
redisReply* reply = static_cast<redisReply*>(redisvCommand(context_, format, ap));
va_end(ap);
return reply;
}
redisReply* RedisConnection::executeCommandArgv(int argc, const char** argv, const size_t* argvlen) {
std::lock_guard<std::mutex> lock(mutex_);
if (!context_ || context_->err) {
return nullptr;
}
/**
* @函数:redisCommandArgv
* @说明:通过数组形式发送命令。
* @优势:
* 1. 二进制安全:argvlen 允许参数中包含 null 字符。
* 2. 性能:避免了 printf 格式的解析开销。
* 3. 安全:防止格式化字符串漏洞。
*/
return static_cast<redisReply*>(redisCommandArgv(context_, argc, argv, argvlen));
}
void RedisConnection::setLastUsedTime(std::time_t time) {
// 简单赋值操作,通常由连接池管理调用
lastUsedTime_ = time;
}
std::time_t RedisConnection::getLastUsedTime() const {
return lastUsedTime_;
}
} // namespace redis
} // namespace dfs
2. 连接池
2.1 redis_connection_pool.h
cpp
复制代码
/**
* @file redis_connection_pool.h
* @brief Redis连接池模块
* @author DFS Team
* @date 2025-02-16
*
* @version 1.0.0
* - 初始版本,实现Redis连接池
*
* 功能说明:
* - 管理多个Redis连接
* - 实现连接复用,减少连接创建开销
* - 支持连接获取超时
* - 线程安全的连接管理
*/
#ifndef DFS_REDIS_REDIS_CONNECTION_POOL_H
#define DFS_REDIS_REDIS_CONNECTION_POOL_H
#include "redis_connection.h"
#include <queue>
#include <vector>
#include <condition_variable>
#include <atomic>
namespace dfs {
namespace redis {
/**
* @brief Redis连接池类
*
* 管理Redis连接的生命周期,实现连接复用
*/
class RedisConnectionPool {
public:
RedisConnectionPool(const RedisConnectionPool&) = delete;
RedisConnectionPool& operator=(const RedisConnectionPool&) = delete;
// 构造函数
RedisConnectionPool(const RedisConfig& config, int maxPoolSize = 10, int minPoolSize = 2);
// 析构函数,自动关闭所有连接
~RedisConnectionPool();
// 初始化连接池,true 初始化成功,false 失败
bool init();
// 获取一个连接,超时时间默认5秒
RedisConnection::Ptr getConnection(int timeoutMs = 5000);
// 归还连接;@param conn 连接指针;@param valid 连接是否有效
void returnConnection(RedisConnection::Ptr conn, bool valid = true);
void close(); // 关闭连接池
int getMaxPoolSize() const; // 获取最大连接数
int getMinPoolSize() const; // 获取最小空闲连接数
int getCurrentSize() const; // 获取当前连接总数
int getIdleSize() const; // 获取空闲连接数
private:
RedisConnection::Ptr createConnection(); // 创建新连接
RedisConfig config_; // 连接配置
int maxPoolSize_; // 最大连接数
int minPoolSize_; // 最小空闲连接数
std::atomic<int> currentSize_{0}; // 当前连接总数
std::atomic<bool> running_{false}; // 运行状态
std::queue<RedisConnection::Ptr> idleConnections_; // 空闲连接队列
std::mutex poolMutex_; // 互斥锁
std::condition_variable poolCond_; // 条件变量
};
}
}
#endif
2.2 redis_connection_pool.cpp
cpp
复制代码
/**
* @file redis_connection_pool.cpp
* @brief Redis 连接池实现文件
* @author DFS Team
* @date 2025-02-16
*
* @version 1.0.0
* - 初始版本,实现 Redis 连接池管理
*
* 设计目标:
* - 复用 Redis 连接,避免频繁创建/销毁连接的开销
* - 控制并发连接数,防止 Redis 服务器被压垮
* - 提供线程安全的连接获取/归还接口
* - 支持最小/最大连接数配置
*
* 核心机制:
* - 使用 std::queue 维护空闲连接队列
* - 使用互斥锁保护队列操作
* - 使用条件变量实现等待超时机制
* - 连接有效性检查(归还时/获取时)
*/
#include "redis_connection_pool.h"
#include "../common/logger.h"
namespace dfs {
namespace redis {
/**
* @brief 构造函数
* @param config Redis 连接配置(所有连接共享相同配置)
* @param maxPoolSize 最大连接数(硬限制,防止资源耗尽)
* @param minPoolSize 最小连接数(初始化时预创建,减少首次请求延迟)
*
* @note 构造函数仅初始化配置,不创建实际连接(延迟初始化)
* @note maxPoolSize 应 >= minPoolSize,否则逻辑可能异常
*/
RedisConnectionPool::RedisConnectionPool(const RedisConfig& config, int maxPoolSize, int minPoolSize)
: config_(config), maxPoolSize_(maxPoolSize), minPoolSize_(minPoolSize) {}
/**
* @brief 析构函数
* @note 自动调用 close() 释放所有连接资源
* @warning 确保在程序退出前正确销毁连接池,防止资源泄漏
*/
RedisConnectionPool::~RedisConnectionPool() {
close();
}
/**
* @brief 初始化连接池
* @return true 初始化成功(至少创建了 1 个连接或 minPoolSize=0)
* @return false 初始化失败(无法创建任何连接)
*
* @note 线程安全:使用 poolMutex_ 保护初始化过程
* @note 预创建 minPoolSize_ 个连接,避免首次请求时的连接建立延迟
*
* 初始化流程:
* 1. 设置 running_ = true(标记池可用)
* 2. 循环创建 minPoolSize_ 个连接
* 3. 将成功连接的实例加入空闲队列
* 4. 更新 currentSize_ 计数器
*/
bool RedisConnectionPool::init() {
// 加锁保护初始化过程,防止多线程同时初始化
std::lock_guard<std::mutex> lock(poolMutex_);
running_ = true;
// 预创建最小连接数
for (int i = 0; i < minPoolSize_; ++i) {
auto conn = createConnection();
if (conn) {
// 将成功连接的实例加入空闲队列
// 注意:此时连接已建立并完成认证/选库
idleConnections_.push(conn);
currentSize_++;
} else {
// 连接创建失败,记录日志但继续尝试创建剩余连接
LOG_WARN("Failed to create connection %d/%d during pool init", i + 1, minPoolSize_);
}
}
LOG_INFO("Redis connection pool initialized, size: %d", currentSize_.load());
// 返回成功条件:有可用连接 或 最小连接数为 0
return currentSize_ > 0 || minPoolSize_ == 0;
}
/**
* @brief 从连接池获取一个可用连接
* @param timeoutMs 等待超时时间(毫秒)
* - 0: 立即返回,不等待
* - >0: 等待指定时间,直到有连接可用或超时
* - <0: 无限等待(不推荐,可能导致线程阻塞)
* @return RedisConnection::Ptr 可用连接智能指针
* @return nullptr 获取失败(超时/池已满/连接创建失败)
*
* @note 线程安全:使用 unique_lock 配合条件变量实现等待机制
* @note 返回的连接使用后必须调用 returnConnection() 归还
* @warning 忘记归还会导致连接池耗尽(连接泄漏)
*
* 获取逻辑优先级:
* 1. 空闲队列有连接 → 直接返回(最快)
* 2. 当前连接数 < 最大限制 → 创建新连接返回
* 3. 等待超时时间内有空闲连接 → 返回
* 4. 以上都失败 → 返回 nullptr
*/
RedisConnection::Ptr RedisConnectionPool::getConnection(int timeoutMs) {
// 使用 unique_lock 以便配合条件变量 wait_for
std::unique_lock<std::mutex> lock(poolMutex_);
// 【优先级 1】从空闲队列获取连接
if (!idleConnections_.empty()) {
auto conn = idleConnections_.front();
idleConnections_.pop();
// 检查连接是否仍然有效(可能已断开)
if (conn->isConnected()) {
// 有效连接,直接返回给调用者
return conn;
}
// 连接已失效,递减计数器,继续尝试其他获取方式
currentSize_--;
LOG_WARN("Got invalid connection from pool, discarding");
}
// 【优先级 2】创建新连接(如果未达到上限)
if (currentSize_ < maxPoolSize_) {
// 注意:createConnection 内部会加锁,但此处已持有锁
// 需要临时释放锁或使用无锁版本的创建逻辑
// 当前实现中 createConnection 不加锁,所以安全
auto conn = createConnection();
if (conn) {
currentSize_++;
return conn;
}
}
// 【优先级 3】等待空闲连接(如果指定了超时时间)
if (timeoutMs > 0) {
/**
* @函数:condition_variable::wait_for
* @说明:阻塞当前线程,直到以下条件之一满足:
* 1. 条件谓词返回 true(有空闲连接)
* 2. 超时时间到达
* @注意:wait_for 会自动释放锁,唤醒后重新获取锁
*/
if (poolCond_.wait_for(lock, std::chrono::milliseconds(timeoutMs),
[this] { return !idleConnections_.empty(); })) {
// 条件满足(有空闲连接),再次尝试获取
auto conn = idleConnections_.front();
idleConnections_.pop();
if (conn->isConnected()) {
return conn;
}
currentSize_--;
} else {
// 超时,记录日志
LOG_WARN("Connection pool wait timeout (%d ms)", timeoutMs);
}
}
// 所有方式都失败,返回 nullptr
return nullptr;
}
/**
* @brief 将连接归还到连接池
* @param conn 要归还的连接智能指针
* @param valid 连接是否有效(由调用者判断)
* - true: 连接正常,可放回空闲队列复用
* - false: 连接异常,直接销毁,不放回队列
*
* @note 线程安全:使用 lock_guard 保护队列操作
* @note 归还后会自动通知等待中的线程(notify_one)
* @warning 必须确保每个 getConnection() 都有对应的 returnConnection()
*
* 归还逻辑:
* 1. 检查池是否正在运行(关闭中则直接销毁)
* 2. 检查连接有效性(无效则销毁)
* 3. 更新最后使用时间(用于空闲检测)
* 4. 放入空闲队列,唤醒等待线程
*/
void RedisConnectionPool::returnConnection(RedisConnection::Ptr conn, bool valid) {
if (!conn) return;
std::lock_guard<std::mutex> lock(poolMutex_);
// 检查是否应该销毁连接而不是归还
if (!running_ || !valid || !conn->isConnected()) {
// 连接池关闭中/连接无效/连接已断开 → 直接销毁
currentSize_--;
LOG_WARN("Connection discarded (running=%d, valid=%d, connected=%d)",
running_, valid, conn->isConnected());
return;
}
// 更新最后使用时间(用于连接池的空闲超时检测)
conn->setLastUsedTime(std::time(nullptr));
// 放回空闲队列,供其他线程复用
idleConnections_.push(conn);
/**
* @函数:condition_variable::notify_one
* @说明:唤醒一个正在 wait/wait_for 的线程
* @用途:通知等待连接的线程"现在有空闲连接了"
* @注意:必须在锁持有期间或释放后立即调用
*/
poolCond_.notify_one();
}
/**
* @brief 关闭连接池,释放所有资源
* @note 线程安全:设置 running_=false 并清空队列
* @warning 调用后所有 getConnection() 将返回 nullptr
* @warning 确保没有线程正在使用连接时再调用 close()
*
* 关闭流程:
* 1. 设置 running_ = false(标记池不可用)
* 2. 清空空闲队列(shared_ptr 析构会自动关闭连接)
* 3. 重置计数器
* 4. 唤醒所有等待线程(让它们快速失败)
*/
void RedisConnectionPool::close() {
std::lock_guard<std::mutex> lock(poolMutex_);
running_ = false;
// 清空空闲队列
// 注意:shared_ptr 出队后引用计数减 1,为 0 时自动调用 RedisConnection 析构
while (!idleConnections_.empty()) {
idleConnections_.pop();
}
currentSize_ = 0;
// 唤醒所有等待中的线程,让它们快速返回 nullptr
poolCond_.notify_all();
LOG_INFO("Redis connection pool closed");
}
/**
* @brief 创建新的 Redis 连接
* @return RedisConnection::Ptr 新创建的连接
* @return nullptr 创建失败
*
* @note 此函数内部会调用 RedisConnection::connect()
* @note 调用者需确保已持有 poolMutex_ 或此函数在安全上下文中调用
*
* 创建流程:
* 1. 使用 config_ 创建 RedisConnection 对象
* 2. 调用 connect() 建立物理连接
* 3. 成功则返回,失败则返回 nullptr
*/
RedisConnection::Ptr RedisConnectionPool::createConnection() {
// 使用 shared_ptr 管理连接生命周期
auto conn = std::make_shared<RedisConnection>(config_);
// 尝试建立连接(包括认证、选库等)
if (conn->connect()) {
return conn;
}
// 连接失败,shared_ptr 析构会自动清理
return nullptr;
}
// 获取最大连接数配置
int RedisConnectionPool::getMaxPoolSize() const { return maxPoolSize_; }
// 获取最小连接数配置
int RedisConnectionPool::getMinPoolSize() const { return minPoolSize_; }
/**
* @brief 获取当前连接总数(空闲 + 使用中)
* @return int 当前连接数
* @note 使用 atomic 保证读取的线程安全性
*/
int RedisConnectionPool::getCurrentSize() const {
return currentSize_.load();
}
/**
* @brief 获取空闲连接数
* @return int 空闲连接数
* @note 需要加锁读取 queue.size(),因为 std::queue 不是线程安全的
* @warning 使用 const_cast 去除 mutex 的 const 属性以加锁(只读操作,安全)
*/
int RedisConnectionPool::getIdleSize() const {
// 注意:const 成员函数中修改 mutex 状态是允许的(mutable)
// 但这里 poolMutex_ 不是 mutable,所以用 const_cast
// 更好的做法是将 poolMutex_ 声明为 mutable
std::lock_guard<std::mutex> lock(const_cast<std::mutex&>(poolMutex_));
return static_cast<int>(idleConnections_.size());
}
} // namespace redis
} // namespace dfs