Redis:(3) Lua 与 Redis、基于连接池的 Facade 模式封装

1. Lua

Lua 是一种轻量、高效、可嵌入的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能

1.1 Lua 特性

  • 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
  • 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
  • 其它特性 :
    • 支持面向过程编程和函数式编程(;
    • 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
    • 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
    • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。

1.2 变量与数据类型

Lua 复制代码
-- 变量无需声明类型
local name = "Lua"
local age = 10
local pi = 3.14159
local isTrue = true
local data = nil

-- 查看类型
print(type(name))  -- string
print(type(age))   -- number

-- 数组
local arr = {1, 2, 3}

-- 字典
local person = {
    name = "Alice",
    age = 25
}

-- 访问
print(arr[1])         -- 1(索引从1开始)
print(person.name)    -- Alice
print(person["age"])  -- 25

-- 遍历
for i, v in ipairs(arr) do
    print(i, v)
end

for k, v in pairs(person) do
    print(k, v)
end

1.3 为什么 Redis 需要 Lua

Redis 本身是单线程的(处理命令的主线程),它的每个原生命令(如 SET, GET, INCR)都是原子操作 的。但是,当需要执行多个命令的组合时,原子性就无法保证了。

  • 场景:想"读取一个值,判断是否大于 10,如果大于 10 则加 1,否则设为 0"。
  • 不用 Lua :客户端需要发送 GET -> 客户端判断 -> 客户端发送 SET 或 INCR。
    • 问题 :在 GET 和 SET 之间,其他客户端可能修改了这个值(竞态条件)。
  • Redis 事务 (MULTI/EXEC) :虽然能打包命令,但不支持逻辑判断(没有 if/else),且出错时不会回滚。
  • 使用 Lua :将这段逻辑写成脚本,发送给 Redis 服务器执行。
    • 结果 :Redis 会一次性、原子性地执行完整个脚本,期间不会插入其他客户端的命令。

Lua 脚本让 Redis 支持了服务端逻辑 ,并保证了多命令操作的原子性。

Redis 服务器内部内置了一个 Lua 解释器(基于 Lua 5.1 修改版)。

  1. 发送脚本:客户端通过 EVAL 或 EVALSHA 命令将 Lua 代码发送给 Redis。
  2. 执行环境:Redis 主线程会暂停处理其他命令,转而执行这个 Lua 脚本。
  3. 沙箱环境 :为了安全,Redis 限制了 Lua 的功能。
    • 禁止:不能加载外部模块、不能访问文件系统、不能执行耗时过长的操作。
    • 允许:只能调用 Redis 提供的命令(通过 redis.call() 或 redis.pcall())。
  4. 返回结果:脚本执行完毕后,将结果返回给客户端,Redis 主线程恢复处理其他命令。
命令 说明 适用场景
EVAL script numkeys key key ... arg arg ... 直接发送脚本内容执行。 调试、脚本较短时。
EVALSHA sha1 numkeys key key ... arg arg ... 发送脚本的 SHA1 哈希值执行。 生产环境推荐。减少网络传输流量。
SCRIPT LOAD script 将脚本加载到服务器缓存,返回 SHA1。 配合 EVALSHA 使用,预加载脚本。
SCRIPT EXISTS sha1 sha1 ... 检查脚本是否已缓存。 确保 EVALSHA 前脚本存在。
SCRIPT FLUSH 清除所有缓存的脚本。 维护时使用。

1.4 优势

1.4.1 保证原子性 (Atomicity)

这是最大的优势。因为 Redis 是单线程执行 Lua 脚本的,脚本执行期间,不会有其他命令插入

  • 例如:检查库存 -> 扣减库存 -> 记录日志。这三个步骤在 Lua 中是原子的,不会出现超卖。

1.4.2 减少网络开销

  • 不用 Lua:客户端与 Redis 交互 5 次(5 个 RTT 网络往返)。
  • 用 Lua:客户端发送 1 次脚本,Redis 内部执行 5 个命令,返回 1 次结果(1 个 RTT)。
  • 在高并发、高延迟网络环境下,性能提升显著。

1.4.3 复用性与缓存

使用 EVALSHA 时,脚本只需传输一次。后续调用只需发送 40 字节的 SHA1 哈希值,极大节省带宽。Redis 服务器会缓存脚本,无需重复解析。

1.4.4 业务逻辑

可以在服务端实现复杂的逻辑,如:

  • 滑动窗口限流。
  • 分布式锁的自动续期与释放。
  • 复杂的数据结构转换。

2. 代码实现

项目结构

cpp 复制代码
project/
├── common/
│   └── logger.h          # 假设的日志头文件
├── redis/
│   ├── redis_connection.h
│   ├── redis_connection.cpp
│   ├── redis_connection_pool.h
│   ├── redis_connection_pool.cpp
│   ├── redis_cache.h
│   └── redis_cache.cpp
├── main.cpp              # 下面的使用示例
└── CMakeLists.txt        # 或 Makefile

2.1 redis_cache.h

cpp 复制代码
/**
 * @file redis_cache.h
 * @brief Redis 缓存操作高级封装模块
 * @author DFS Team
 * @date 2025-02-16
 * 
 * @version 1.0.0
 * - 初始版本,实现 Redis 常用操作封装
 * 
 * 功能说明:
 * - 基于 RedisConnectionPool 的高级 Facade 模式封装
 * - 屏蔽 hiredis 底层细节,提供 C++ 风格 API
 * - 支持 String/Hash/List/Set/ZSet 五种核心数据结构
 * - 支持 Lua 脚本执行
 * - 自动管理连接获取与归还,防止连接泄漏
 * 
 * 线程安全:
 * - 本类无状态,线程安全依赖于底层的 RedisConnectionPool
 * - 多线程可共享同一个 RedisCache 实例
 * 
 * 内存管理:
 * - 内部自动管理 redisReply 内存
 * - 返回值使用 std::optional 区分"空值"与"空字符串"
 */

#ifndef DFS_REDIS_REDIS_CACHE_H
#define DFS_REDIS_REDIS_CACHE_H

#include "redis_connection_pool.h"
#include <optional>      // C++17, 用于表示可能为空的返回值
#include <vector>
#include <unordered_map>
#include <string>
#include <memory>

namespace dfs {
namespace redis {

/**
 * @brief Redis 缓存操作类
 * 
 * 封装常用的 Redis 操作,提供简洁的 API 接口。
 * 此类不直接持有 redisContext,而是通过连接池按需获取连接。
 */
class RedisCache {
public:
    // 使用 shared_ptr 管理 RedisCache 生命周期
    using Ptr = std::shared_ptr<RedisCache>;

    /**
     * @brief 构造函数
     * @param pool Redis 连接池共享指针
     * @note RedisCache 不拥有连接池,仅持有引用,确保连接池生命周期长于缓存对象
     */
    explicit RedisCache(std::shared_ptr<RedisConnectionPool> pool);

    /**
     * @brief 析构函数
     * @note 默认即可,连接归还由具体方法保证
     */
    ~RedisCache() = default;

    // =========================================================================
    // String 类型操作
    // =========================================================================

    /**
     * @brief 设置键值对
     * @param key 键名
     * @param value 值
     * @param ttlSeconds 过期时间(秒),-1 表示永不过期
     * @return true 设置成功
     * @return false 设置失败(连接错误/Redis 错误)
     * @note 对应命令:SET key value [EX seconds]
     */
    bool set(const std::string& key, const std::string& value, int ttlSeconds = -1);

    /**
     * @brief 获取键值
     * @param key 键名
     * @return std::optional<std::string> 存在则返回值,不存在则为 nullopt
     * @note 对应命令:GET key
     * @note 使用 optional 区分"键不存在"和"值为空字符串"
     */
    std::optional<std::string> get(const std::string& key);

    /**
     * @brief 删除键
     * @param key 键名
     * @return true 删除成功(键存在)
     * @return false 删除失败或键不存在
     * @note 对应命令:DEL key
     */
    bool del(const std::string& key);

    /**
     * @brief 检查键是否存在
     * @param key 键名
     * @return true 存在
     * @return false 不存在
     * @note 对应命令:EXISTS key
     */
    bool exists(const std::string& key);

    /**
     * @brief 设置键的过期时间
     * @param key 键名
     * @param ttlSeconds 过期时间(秒)
     * @return true 设置成功
     * @return false 设置失败或键不存在
     * @note 对应命令:EXPIRE key seconds
     */
    bool expire(const std::string& key, int ttlSeconds);

    /**
     * @brief 获取键的剩余生存时间
     * @param key 键名
     * @return int64_t 剩余秒数
     *         -1: 键存在但无过期时间
     *         -2: 键不存在
     * @note 对应命令:TTL key
     */
    int64_t ttl(const std::string& key);

    /**
     * @brief 仅在键不存在时设置值 (Set if Not Exists)
     * @param key 键名
     * @param value 值
     * @param ttlSeconds 过期时间(秒),-1 表示永不过期
     * @return true 设置成功(键之前不存在)
     * @return false 设置失败(键已存在或错误)
     * @note 对应命令:SET key value NX [EX seconds]
     * @用途:分布式锁、幂等性检查
     */
    bool setNx(const std::string& key, const std::string& value, int ttlSeconds = -1);

    /**
     * @brief 自增 1
     * @param key 键名
     * @return int64_t 自增后的值,失败返回 0
     * @note 对应命令:INCR key
     */
    int64_t incr(const std::string& key);

    /**
     * @brief 自增指定值
     * @param key 键名
     * @param increment 增量
     * @return int64_t 自增后的值,失败返回 0
     * @note 对应命令:INCRBY key increment
     */
    int64_t incrBy(const std::string& key, int64_t increment);

    /**
     * @brief 自减 1
     * @param key 键名
     * @return int64_t 自减后的值,失败返回 0
     * @note 对应命令:DECR key
     */
    int64_t decr(const std::string& key);

    /**
     * @brief 自减指定值
     * @param key 键名
     * @param decrement 减量
     * @return int64_t 自减后的值,失败返回 0
     * @note 对应命令:DECRBY key decrement (内部实现为 INCRBY -decrement)
     */
    int64_t decrBy(const std::string& key, int64_t decrement);

    // =========================================================================
    // Hash 类型操作
    // =========================================================================

    /**
     * @brief 设置 Hash 字段
     * @param key 键名
     * @param field 字段名
     * @param value 字段值
     * @return true 成功
     * @return false 失败
     * @note 对应命令:HSET key field value
     */
    bool hSet(const std::string& key, const std::string& field, const std::string& value);

    /**
     * @brief 获取 Hash 字段
     * @param key 键名
     * @param field 字段名
     * @return std::optional<std::string> 存在则返回值,否则 nullopt
     * @note 对应命令:HGET key field
     */
    std::optional<std::string> hGet(const std::string& key, const std::string& field);

    /**
     * @brief 删除 Hash 字段
     * @param key 键名
     * @param field 字段名
     * @return true 删除成功(字段存在)
     * @return false 删除失败或字段不存在
     * @note 对应命令:HDEL key field
     */
    bool hDel(const std::string& key, const std::string& field);

    /**
     * @brief 获取 Hash 所有字段和值
     * @param key 键名
     * @return std::unordered_map<std::string, std::string> 字段 - 值映射
     * @note 对应命令:HGETALL key
     * @warning 大 Hash 操作可能阻塞 Redis,谨慎使用
     */
    std::unordered_map<std::string, std::string> hGetAll(const std::string& key);

    /**
     * @brief 检查 Hash 字段是否存在
     * @param key 键名
     * @param field 字段名
     * @return true 存在
     * @return false 不存在
     * @note 对应命令:HEXISTS key field
     */
    bool hExists(const std::string& key, const std::string& field);

    /**
     * @brief Hash 字段自增
     * @param key 键名
     * @param field 字段名
     * @param increment 增量
     * @return int64_t 自增后的值,失败返回 0
     * @note 对应命令:HINCRBY key field increment
     */
    int64_t hIncrBy(const std::string& key, const std::string& field, int64_t increment);

    // =========================================================================
    // List 类型操作
    // =========================================================================

    /**
     * @brief 左侧推入列表
     * @param key 键名
     * @param value 值
     * @return int64_t 列表长度,失败返回 0
     * @note 对应命令:LPUSH key value
     */
    int64_t lPush(const std::string& key, const std::string& value);

    /**
     * @brief 右侧推入列表
     * @param key 键名
     * @param value 值
     * @return int64_t 列表长度,失败返回 0
     * @note 对应命令:RPUSH key value
     */
    int64_t rPush(const std::string& key, const std::string& value);

    /**
     * @brief 左侧弹出元素
     * @param key 键名
     * @return std::optional<std::string> 元素值,列表空则 nullopt
     * @note 对应命令:LPOP key
     */
    std::optional<std::string> lPop(const std::string& key);

    /**
     * @brief 右侧弹出元素
     * @param key 键名
     * @return std::optional<std::string> 元素值,列表空则 nullopt
     * @note 对应命令:RPOP key
     */
    std::optional<std::string> rPop(const std::string& key);

    /**
     * @brief 获取列表范围
     * @param key 键名
     * @param start 起始索引 (0-based, 负数表示倒数)
     * @param stop 结束索引 (包含)
     * @return std::vector<std::string> 元素列表
     * @note 对应命令:LRANGE key start stop
     */
    std::vector<std::string> lRange(const std::string& key, int64_t start, int64_t stop);

    /**
     * @brief 获取列表长度
     * @param key 键名
     * @return int64_t 长度,失败返回 0
     * @note 对应命令:LLEN key
     */
    int64_t lLen(const std::string& key);

    // =========================================================================
    // Set 类型操作
    // =========================================================================

    /**
     * @brief 添加集合成员
     * @param key 键名
     * @param member 成员
     * @return int64_t 新添加的成员数量 (0 或 1)
     * @note 对应命令:SADD key member
     */
    int64_t sAdd(const std::string& key, const std::string& member);

    /**
     * @brief 移除集合成员
     * @param key 键名
     * @param member 成员
     * @return int64_t 被移除的成员数量 (0 或 1)
     * @note 对应命令:SREM key member
     */
    int64_t sRem(const std::string& key, const std::string& member);

    /**
     * @brief 判断成员是否在集合中
     * @param key 键名
     * @param member 成员
     * @return true 在集合中
     * @return false 不在集合中
     * @note 对应命令:SISMEMBER key member
     */
    bool sIsMember(const std::string& key, const std::string& member);

    /**
     * @brief 获取集合所有成员
     * @param key 键名
     * @return std::vector<std::string> 成员列表
     * @note 对应命令:SMEMBERS key
     * @warning 大集合操作可能阻塞 Redis
     */
    std::vector<std::string> sMembers(const std::string& key);

    /**
     * @brief 获取集合基数 (成员数量)
     * @param key 键名
     * @return int64_t 数量,失败返回 0
     * @note 对应命令:SCARD key
     */
    int64_t sCard(const std::string& key);

    // =========================================================================
    // ZSet (Sorted Set) 类型操作
    // =========================================================================

    /**
     * @brief 添加有序集合成员
     * @param key 键名
     * @param member 成员
     * @param score 分数
     * @return int64_t 新添加的成员数量 (0 或 1)
     * @note 对应命令:ZADD key score member
     */
    int64_t zAdd(const std::string& key, const std::string& member, double score);

    /**
     * @brief 移除有序集合成员
     * @param key 键名
     * @param member 成员
     * @return int64_t 被移除的成员数量
     * @note 对应命令:ZREM key member
     */
    int64_t zRem(const std::string& key, const std::string& member);

    /**
     * @brief 获取成员分数
     * @param key 键名
     * @param member 成员
     * @return std::optional<double> 分数,成员不存在则 nullopt
     * @note 对应命令:ZSCORE key member
     */
    std::optional<double> zScore(const std::string& key, const std::string& member);

    /**
     * @brief 获取成员排名 (从低到高)
     * @param key 键名
     * @param member 成员
     * @return int64_t 排名 (0-based),成员不存在返回 -1
     * @note 对应命令:ZRANK key member
     */
    int64_t zRank(const std::string& key, const std::string& member);

    /**
     * @brief 获取指定排名范围的成员
     * @param key 键名
     * @param start 起始排名
     * @param stop 结束排名
     * @return std::vector<std::string> 成员列表
     * @note 对应命令:ZRANGE key start stop
     */
    std::vector<std::string> zRange(const std::string& key, int64_t start, int64_t stop);

    // =========================================================================
    // 高级功能
    // =========================================================================

    /**
     * @brief 执行 Lua 脚本
     * @param script Lua 脚本内容
     * @param keys 脚本中使用的键列表 (对应 KEYS 数组)
     * @param args 脚本中使用的参数列表 (对应 ARGV 数组)
     * @param result 输出参数,存储脚本返回的整数值
     * @return true 执行成功
     * @return false 执行失败
     * @note 对应命令:EVAL script numkeys key [key ...] arg [arg ...]
     * @warning 结果仅支持整数返回值判断,复杂返回值需扩展
     */
    bool executeLuaScript(const std::string& script, const std::vector<std::string>& keys,
                         const std::vector<std::string>& args, int64_t& result);

private:
    /**
     * @brief 安全释放 redisReply 对象
     * @param reply 响应指针
     * @note 封装 freeReplyObject,增加空指针检查
     */
    void freeReply(redisReply* reply);

    std::shared_ptr<RedisConnectionPool> pool_;  ///< Redis 连接池
};

} // namespace redis
} // namespace dfs

#endif // DFS_REDIS_REDIS_CACHE_H

2.2 redis_cache.cpp

cpp 复制代码
/**
 * @file redis_cache.cpp
 * @brief Redis 缓存操作实现文件
 * @author DFS Team
 * @date 2025-02-16
 * 
 * 实现细节:
 * - 所有方法遵循 "获取连接 -> 执行命令 -> 处理响应 -> 归还连接" 模式
 * - 严格检查 redisReply 类型,防止类型转换错误
 * - 使用 std::string(str, len) 保证二进制安全 (支持 null 字符)
 * - 异常安全:即使发生错误,连接也会被归还到池中
 */

#include "redis_cache.h"
#include <cstring>
#include <stdexcept>

namespace dfs {
namespace redis {

// 构造函数:初始化连接池指针
RedisCache::RedisCache(std::shared_ptr<RedisConnectionPool> pool)
    : pool_(std::move(pool)) {}

/**
 * @brief 安全释放 redisReply 对象
 * @note hiredis 的 freeReplyObject 接受 NULL 是安全的,但显式检查更清晰
 */
void RedisCache::freeReply(redisReply* reply) {
    if (reply) {
        freeReplyObject(reply);
    }
}

// =========================================================================
// String 类型实现
// =========================================================================

bool RedisCache::set(const std::string& key, const std::string& value, int ttlSeconds) {
    // 1. 从连接池获取连接
    auto conn = pool_->getConnection();
    if (!conn) return false;

    redisReply* reply = nullptr;
    // 2. 根据是否有 TTL 构建不同命令
    if (ttlSeconds > 0) {
        // SET key value EX seconds (原子性设置值 + 过期时间)
        reply = conn->executeCommand("SET %s %s EX %d", key.c_str(), value.c_str(), ttlSeconds);
    } else {
        // SET key value
        reply = conn->executeCommand("SET %s %s", key.c_str(), value.c_str());
    }

    // 3. 检查响应类型 (SET 成功返回 STATUS "OK")
    // 只要不是 ERROR 类型,通常视为成功
    bool success = reply && reply->type != REDIS_REPLY_ERROR;
    
    // 4. 清理资源
    freeReply(reply);
    // 5. 归还连接 (关键步骤,防止连接池耗尽)
    pool_->returnConnection(conn);
    return success;
}

std::optional<std::string> RedisCache::get(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return std::nullopt;

    redisReply* reply = conn->executeCommand("GET %s", key.c_str());
    std::optional<std::string> result;

    // 检查响应是否为字符串类型
    if (reply && reply->type == REDIS_REPLY_STRING) {
        // 【重要】使用 str 和 len 构造字符串,而非 reply->str
        // 原因:Redis 值可能包含 \0 字符,strlen 会截断
        result = std::string(reply->str, reply->len);
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

bool RedisCache::del(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    // DEL 返回被删除的 key 数量 (Integer)
    redisReply* reply = conn->executeCommand("DEL %s", key.c_str());
    bool success = reply && reply->type == REDIS_REPLY_INTEGER;
    freeReply(reply);
    pool_->returnConnection(conn);
    return success;
}

bool RedisCache::exists(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    // EXISTS 返回 1 或 0
    redisReply* reply = conn->executeCommand("EXISTS %s", key.c_str());
    bool result = reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1;
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

bool RedisCache::expire(const std::string& key, int ttlSeconds) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    // EXPIRE 返回 1 (设置成功) 或 0 (key 不存在)
    redisReply* reply = conn->executeCommand("EXPIRE %s %d", key.c_str(), ttlSeconds);
    bool success = reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1;
    freeReply(reply);
    pool_->returnConnection(conn);
    return success;
}

int64_t RedisCache::ttl(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return -2; // 默认返回 -2 表示连接失败或 key 不存在

    redisReply* reply = conn->executeCommand("TTL %s", key.c_str());
    int64_t result = -2;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

bool RedisCache::setNx(const std::string& key, const std::string& value, int ttlSeconds) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    redisReply* reply = nullptr;
    if (ttlSeconds > 0) {
        // SET key value NX EX seconds (原子性:不存在则设置 + 过期)
        reply = conn->executeCommand("SET %s %s NX EX %d", key.c_str(), value.c_str(), ttlSeconds);
    } else {
        // SET key value NX (不存在则设置)
        reply = conn->executeCommand("SET %s %s NX", key.c_str(), value.c_str());
    }

    // NX 模式:成功返回 STATUS "OK", 失败返回 NIL (reply->type == REDIS_REPLY_NIL)
    bool success = reply && reply->type == REDIS_REPLY_STATUS;
    freeReply(reply);
    pool_->returnConnection(conn);
    return success;
}

int64_t RedisCache::incr(const std::string& key) {
    return incrBy(key, 1);
}

int64_t RedisCache::incrBy(const std::string& key, int64_t increment) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    // INCRBY 返回自增后的整数值
    redisReply* reply = conn->executeCommand("INCRBY %s %lld", key.c_str(), increment);
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::decr(const std::string& key) {
    return decrBy(key, 1);
}

int64_t RedisCache::decrBy(const std::string& key, int64_t decrement) {
    // 优化:DECRBY 本质是 INCRBY 负数
    return incrBy(key, -decrement);
}

// =========================================================================
// Hash 类型实现
// =========================================================================

bool RedisCache::hSet(const std::string& key, const std::string& field, const std::string& value) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    redisReply* reply = conn->executeCommand("HSET %s %s %s", key.c_str(), field.c_str(), value.c_str());
    bool success = reply && reply->type != REDIS_REPLY_ERROR;
    freeReply(reply);
    pool_->returnConnection(conn);
    return success;
}

std::optional<std::string> RedisCache::hGet(const std::string& key, const std::string& field) {
    auto conn = pool_->getConnection();
    if (!conn) return std::nullopt;

    redisReply* reply = conn->executeCommand("HGET %s %s", key.c_str(), field.c_str());
    std::optional<std::string> result;

    if (reply && reply->type == REDIS_REPLY_STRING) {
        result = std::string(reply->str, reply->len);
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

bool RedisCache::hDel(const std::string& key, const std::string& field) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    // HDEL 返回被删除的字段数量
    redisReply* reply = conn->executeCommand("HDEL %s %s", key.c_str(), field.c_str());
    bool success = reply && reply->type == REDIS_REPLY_INTEGER && reply->integer > 0;
    freeReply(reply);
    pool_->returnConnection(conn);
    return success;
}

std::unordered_map<std::string, std::string> RedisCache::hGetAll(const std::string& key) {
    auto conn = pool_->getConnection();
    std::unordered_map<std::string, std::string> result;

    if (!conn) return result;

    redisReply* reply = conn->executeCommand("HGETALL %s", key.c_str());

    // HGETALL 返回数组:[field1, value1, field2, value2, ...]
    if (reply && reply->type == REDIS_REPLY_ARRAY) {
        // 步长为 2,成对读取
        for (size_t i = 0; i + 1 < reply->elements; i += 2) {
            // 确保元素类型正确
            if (reply->element[i] && reply->element[i + 1]) {
                std::string field(reply->element[i]->str, reply->element[i]->len);
                std::string value(reply->element[i + 1]->str, reply->element[i + 1]->len);
                result[field] = value;
            }
        }
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

bool RedisCache::hExists(const std::string& key, const std::string& field) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    redisReply* reply = conn->executeCommand("HEXISTS %s %s", key.c_str(), field.c_str());
    bool result = reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1;
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::hIncrBy(const std::string& key, const std::string& field, int64_t increment) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("HINCRBY %s %s %lld", key.c_str(), field.c_str(), increment);
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

// =========================================================================
// List 类型实现
// =========================================================================

int64_t RedisCache::lPush(const std::string& key, const std::string& value) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("LPUSH %s %s", key.c_str(), value.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::rPush(const std::string& key, const std::string& value) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("RPUSH %s %s", key.c_str(), value.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

std::optional<std::string> RedisCache::lPop(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return std::nullopt;

    redisReply* reply = conn->executeCommand("LPOP %s", key.c_str());
    std::optional<std::string> result;

    // 列表为空时 Redis 返回 NIL
    if (reply && reply->type == REDIS_REPLY_STRING) {
        result = std::string(reply->str, reply->len);
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

std::optional<std::string> RedisCache::rPop(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return std::nullopt;

    redisReply* reply = conn->executeCommand("RPOP %s", key.c_str());
    std::optional<std::string> result;

    if (reply && reply->type == REDIS_REPLY_STRING) {
        result = std::string(reply->str, reply->len);
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

std::vector<std::string> RedisCache::lRange(const std::string& key, int64_t start, int64_t stop) {
    auto conn = pool_->getConnection();
    std::vector<std::string> result;

    if (!conn) return result;

    redisReply* reply = conn->executeCommand("LRANGE %s %lld %lld", key.c_str(), start, stop);

    if (reply && reply->type == REDIS_REPLY_ARRAY) {
        result.reserve(reply->elements); // 预分配内存优化性能
        for (size_t i = 0; i < reply->elements; ++i) {
            if (reply->element[i]) {
                result.emplace_back(reply->element[i]->str, reply->element[i]->len);
            }
        }
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::lLen(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("LLEN %s", key.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

// =========================================================================
// Set 类型实现
// =========================================================================

int64_t RedisCache::sAdd(const std::string& key, const std::string& member) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("SADD %s %s", key.c_str(), member.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::sRem(const std::string& key, const std::string& member) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("SREM %s %s", key.c_str(), member.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

bool RedisCache::sIsMember(const std::string& key, const std::string& member) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    redisReply* reply = conn->executeCommand("SISMEMBER %s %s", key.c_str(), member.c_str());
    bool result = reply && reply->type == REDIS_REPLY_INTEGER && reply->integer == 1;
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

std::vector<std::string> RedisCache::sMembers(const std::string& key) {
    auto conn = pool_->getConnection();
    std::vector<std::string> result;

    if (!conn) return result;

    redisReply* reply = conn->executeCommand("SMEMBERS %s", key.c_str());

    if (reply && reply->type == REDIS_REPLY_ARRAY) {
        result.reserve(reply->elements);
        for (size_t i = 0; i < reply->elements; ++i) {
            if (reply->element[i]) {
                result.emplace_back(reply->element[i]->str, reply->element[i]->len);
            }
        }
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::sCard(const std::string& key) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("SCARD %s", key.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

// =========================================================================
// ZSet 类型实现
// =========================================================================

int64_t RedisCache::zAdd(const std::string& key, const std::string& member, double score) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    // ZADD key score member
    redisReply* reply = conn->executeCommand("ZADD %s %f %s", key.c_str(), score, member.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::zRem(const std::string& key, const std::string& member) {
    auto conn = pool_->getConnection();
    if (!conn) return 0;

    redisReply* reply = conn->executeCommand("ZREM %s %s", key.c_str(), member.c_str());
    int64_t result = 0;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

std::optional<double> RedisCache::zScore(const std::string& key, const std::string& member) {
    auto conn = pool_->getConnection();
    if (!conn) return std::nullopt;

    // ZSCORE 返回字符串类型的浮点数
    redisReply* reply = conn->executeCommand("ZSCORE %s %s", key.c_str(), member.c_str());
    std::optional<double> result;

    if (reply && reply->type == REDIS_REPLY_STRING) {
        try {
            result = std::stod(reply->str);
        } catch (...) {
            // 解析失败保持 nullopt
        }
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

int64_t RedisCache::zRank(const std::string& key, const std::string& member) {
    auto conn = pool_->getConnection();
    if (!conn) return -1;

    redisReply* reply = conn->executeCommand("ZRANK %s %s", key.c_str(), member.c_str());
    int64_t result = -1;
    if (reply && reply->type == REDIS_REPLY_INTEGER) {
        result = reply->integer;
    }
    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

std::vector<std::string> RedisCache::zRange(const std::string& key, int64_t start, int64_t stop) {
    auto conn = pool_->getConnection();
    std::vector<std::string> result;

    if (!conn) return result;

    redisReply* reply = conn->executeCommand("ZRANGE %s %lld %lld", key.c_str(), start, stop);

    if (reply && reply->type == REDIS_REPLY_ARRAY) {
        result.reserve(reply->elements);
        for (size_t i = 0; i < reply->elements; ++i) {
            if (reply->element[i]) {
                result.emplace_back(reply->element[i]->str, reply->element[i]->len);
            }
        }
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return result;
}

// =========================================================================
// Lua 脚本实现
// =========================================================================

bool RedisCache::executeLuaScript(const std::string& script, const std::vector<std::string>& keys,
                                         const std::vector<std::string>& args, int64_t& result) {
    auto conn = pool_->getConnection();
    if (!conn) return false;

    // 手动构建 EVAL 命令的参数数组
    // 格式:EVAL script numkeys key [key ...] arg [arg ...]
    std::vector<const char*> argv;
    std::vector<size_t> argvlen;

    // 1. 命令名
    argv.push_back("EVAL");
    argvlen.push_back(4);

    // 2. 脚本内容
    argv.push_back(script.c_str());
    argvlen.push_back(script.size());

    // 3. 键的数量 (numkeys)
    std::string numKeys = std::to_string(keys.size());
    argv.push_back(numKeys.c_str());
    argvlen.push_back(numKeys.size());

    // 4. 键列表 (KEYS)
    for (const auto& key : keys) {
        argv.push_back(key.c_str());
        argvlen.push_back(key.size());
    }

    // 5. 参数列表 (ARGV)
    for (const auto& arg : args) {
        argv.push_back(arg.c_str());
        argvlen.push_back(arg.size());
    }

    // 使用 executeCommandArgv 发送二进制安全命令
    redisReply* reply = conn->executeCommandArgv(static_cast<int>(argv.size()), argv.data(), argvlen.data());
    bool success = false;

    if (reply) {
        // 根据返回类型处理结果
        if (reply->type == REDIS_REPLY_INTEGER) {
            result = reply->integer;
            success = true;
        } else if (reply->type == REDIS_REPLY_STATUS || reply->type == REDIS_REPLY_STRING) {
            // 非整数返回也视为执行成功,但 result 保持不变或需扩展解析逻辑
            success = true; 
        }
        // 其他类型 (ERROR, NIL, ARRAY) 视为失败或需特殊处理
    }

    freeReply(reply);
    pool_->returnConnection(conn);
    return success;
}

} // namespace redis
} // namespace dfs

3. 使用示例

cpp 复制代码
/**
 * @file main.cpp
 * @brief RedisCache 封装使用示例
 * @note 编译需要 C++17 支持 (因为使用了 std::optional)
 */

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include "redis/redis_cache.h"

using namespace dfs::redis;

// 模拟日志输出 (如果没有 logger.h)
#define LOG_INFO(fmt, ...) printf("[INFO] " fmt "\n", ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) printf("[ERROR] " fmt "\n", ##__VA_ARGS__)

int main() {
    // =========================================================================
    // 1. 初始化连接池与缓存对象
    // =========================================================================
    
    // 配置 Redis 连接
    RedisConfig config;
    config.host = "127.0.0.1";
    config.port = 6379;
    config.password = "";          // 如果有密码请填写
    config.database = 0;           // 建议使用默认库 0
    config.connectionTimeout = 3000;
    config.socketTimeout = 3000;

    // 创建连接池 (最小 5 个连接,最大 20 个连接)
    auto pool = std::make_shared<RedisConnectionPool>(config, 20, 5);
    
    // 初始化连接池 (预创建最小连接数)
    if (!pool->init()) {
        LOG_ERROR("Failed to initialize Redis connection pool");
        return 1;
    }
    LOG_INFO("Redis pool initialized. Current size: %d", pool->getCurrentSize());

    // 创建缓存操作对象 (共享池)
    auto cache = std::make_shared<RedisCache>(pool);

    // =========================================================================
    // 2. String 类型操作示例
    // =========================================================================
    
    std::cout << "\n=== String Operations ===" << std::endl;

    // 设置值 (永不过期)
    if (cache->set("user:1001:name", "Alice")) {
        LOG_INFO("SET user:1001:name OK");
    }

    // 设置值 (带过期时间 60 秒)
    if (cache->set("session:abc123", "token_data", 60)) {
        LOG_INFO("SET session:abc123 EX 60 OK");
    }

    // 获取值
    auto nameOpt = cache->get("user:1001:name");
    if (nameOpt.has_value()) {
        LOG_INFO("GET user:1001:name = %s", nameOpt.value().c_str());
    } else {
        LOG_INFO("Key user:1001:name not found");
    }

    // 获取不存在的值
    auto notFound = cache->get("user:not_exist");
    if (!notFound.has_value()) {
        LOG_INFO("Key user:not_exist correctly returned nullopt");
    }

    // 自增计数器
    int64_t count = cache->incr("visit:count");
    LOG_INFO("Counter visit:count = %ld", count);

    // =========================================================================
    // 3. Hash 类型操作示例 (存储对象)
    // =========================================================================
    
    std::cout << "\n=== Hash Operations ===" << std::endl;

    // 设置 Hash 字段
    cache->hSet("user:1001", "name", "Alice");
    cache->hSet("user:1001", "age", "25");
    cache->hSet("user:1001", "city", "Beijing");

    // 获取单个字段
    auto ageOpt = cache->hGet("user:1001", "age");
    if (ageOpt.has_value()) {
        LOG_INFO("HGET user:1001 age = %s", ageOpt.value().c_str());
    }

    // 获取所有字段
    auto userMap = cache->hGetAll("user:1001");
    LOG_INFO("HGETALL user:1001 (%zu fields):", userMap.size());
    for (const auto& [field, value] : userMap) {
        LOG_INFO("  %s = %s", field.c_str(), value.c_str());
    }

    // Hash 字段自增
    int64_t newAge = cache->hIncrBy("user:1001", "age", 1);
    LOG_INFO("HINCRBY user:1001 age = %ld", newAge);

    // =========================================================================
    // 4. 分布式锁示例 (setNx)
    // =========================================================================
    
    std::cout << "\n=== Distributed Lock ===" << std::endl;

    std::string lockKey = "lock:order:12345";
    std::string lockValue = "worker_1";
    int lockTtl = 10; // 10 秒自动释放,防止死锁

    // 尝试获取锁
    if (cache->setNx(lockKey, lockValue, lockTtl)) {
        LOG_INFO("Lock acquired! Doing critical work...");
        
        // 模拟业务逻辑
        std::this_thread::sleep_for(std::chrono::seconds(2));
        
        // 业务完成,主动释放锁 (可选,因为有过期时间)
        cache->del(lockKey);
        LOG_INFO("Lock released.");
    } else {
        LOG_INFO("Failed to acquire lock (already locked by others).");
    }

    // =========================================================================
    // 5. List 队列操作示例
    // =========================================================================
    
    std::cout << "\n=== List Queue Operations ===" << std::endl;

    // 右侧推入任务
    cache->rPush("task:queue", "task_1");
    cache->rPush("task:queue", "task_2");
    cache->rPush("task:queue", "task_3");

    // 获取队列长度
    int64_t len = cache->lLen("task:queue");
    LOG_INFO("Queue length: %ld", len);

    // 左侧弹出任务 (FIFO)
    auto taskOpt = cache->lPop("task:queue");
    if (taskOpt.has_value()) {
        LOG_INFO("Processed task: %s", taskOpt.value().c_str());
    }

    // =========================================================================
    // 6. Lua 脚本示例 (原子操作)
    // =========================================================================
    
    std::cout << "\n=== Lua Script ===" << std::endl;

    // 脚本:检查值是否超过限制,如果没超过则自增
    // KEYS[1]: 计数器 key
    // ARGV[1]: 限制值
    // 返回:1 表示成功自增,0 表示超过限制
    std::string script = R"(
        local current = tonumber(redis.call('GET', KEYS[1]) or "0")
        local limit = tonumber(ARGV[1])
        if current < limit then
            redis.call('INCR', KEYS[1])
            return 1
        else
            return 0
        end
    )";

    int64_t luaResult = 0;
    bool success = cache->executeLuaScript(
        script, 
        {"rate:limit:api"},           // KEYS
        {"100"},                      // ARGV (限制 100)
        luaResult
    );

    if (success) {
        LOG_INFO("Lua script executed. Result: %ld (1=OK, 0=Limited)", luaResult);
    } else {
        LOG_ERROR("Lua script execution failed");
    }

    // =========================================================================
    // 7. 多线程安全测试
    // =========================================================================
    
    std::cout << "\n=== Multi-threading Test ===" << std::endl;
    
    std::vector<std::thread> threads;
    int threadCount = 5;
    int opsPerThread = 10;

    // 多个线程共享同一个 cache 对象
    for (int i = 0; i < threadCount; ++i) {
        threads.emplace_back([&, i]() {
            for (int j = 0; j < opsPerThread; ++j) {
                std::string key = "thread:" + std::to_string(i) + ":count";
                cache->incr(key);
            }
        });
    }

    for (auto& t : threads) {
        t.join();
    }

    LOG_INFO("Multi-thread test completed. Check Redis for thread:*:count keys");

    // =========================================================================
    // 8. 程序退出
    // =========================================================================
    
    // pool 和 cache 是 shared_ptr,离开作用域会自动析构
    // RedisConnectionPool 析构时会关闭所有连接
    LOG_INFO("Program exiting, resources will be cleaned up automatically.");

    return 0;
}
相关推荐
码农阿豪4 分钟前
从零到一:Spring Boot快速接入金仓数据库实战
数据库·spring boot·后端
鼎讯信通25 分钟前
风电光缆运维提质增效:G-4000A 光缆故障追踪仪破解风场巡检难题
运维·网络·数据库
三十..1 小时前
MySQL 从入门到高可用架构实战精要
运维·数据库·mysql
cfm_29142 小时前
Redis五大基本数据结构底层了解
数据结构·数据库·redis
真实的菜2 小时前
Redis 从入门到精通(十二):典型业务场景实战 —— 排行榜、限流器、秒杀系统、Session 共享
数据库·redis·python
你想考研啊2 小时前
mysql数据库导出导入
数据库·mysql·oracle
十年编程老舅3 小时前
Linux DRM:底层逻辑与实践架构
数据库·mysql
Qt程序员3 小时前
Linux RCU 原理与应用
linux·c++·内核·linux内核·rcu
The Sheep 20234 小时前
Vue复习
linux·服务器·数据库
qeen874 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习