Redis:(2) hiredis 使用、C++ 封装与连接池

1. 基础使用

1.1 核心数据结构

  1. redisContext: 保存连接状态(Socket、缓冲区、错误信息等)。每个线程应拥有独立的 Context
  2. 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
相关推荐
Desirediscipline1 小时前
cerr << 是C++中用于输出错误信息的标准用法
java·前端·c++·算法
汉克老师2 小时前
GESP2024年6月认证C++二级( 第三部分编程题(2)计数 )
c++·循环结构·枚举算法·gesp二级·gesp2级·数字拆分
王老师青少年编程2 小时前
2020年信奥赛C++提高组csp-s初赛真题及答案解析(选择题11-15)
c++·题解·真题·初赛·信奥赛·csp-s·提高组
今儿敲了吗2 小时前
23| 画展
c++·笔记·学习·算法
代码改善世界2 小时前
【C语言】线性表之顺序表、单链表、双向链表详解及实现
c语言·网络·链表
Desirediscipline4 小时前
#define _CRT_SECURE_NO_WARNINGS 1
开发语言·数据结构·c++·算法·c#·github·visual studio
ShineWinsu4 小时前
对于C++中map和multimap的详细介绍
c++·面试·stl·笔试·map·红黑树·multimap
m0_531237175 小时前
C语言-分支与循环语句练习2
c语言·开发语言·算法
Once_day5 小时前
GCC编译(3)常见编译选项
c语言·c++·编译和链接