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;
}
相关推荐
zlp19921 小时前
Flink DataStream API 消费binlog kafka实践
数据库·flink·kafka
『往事』&白驹过隙;1 小时前
系统编程的内存零拷贝(Zero-Copy)技术
linux·c语言·网络·c++·物联网·iot
l1t2 小时前
利用DeepSeek和qwen 3.5辅助生成SQL优化方法幻灯片视频
数据库·sql·音视频
量子炒饭大师2 小时前
【C++入门】Cyber高维的蜂巢意识 —— 【类与对象】static 成员
开发语言·c++·静态成员变量·static成员
_千思_2 小时前
【小白说】数据库系统概念 4
数据库
ShineWinsu2 小时前
对于stack和queue经典算法题目:155. 最小栈、JZ31 栈的压入、弹出序列和102. 二叉树的层序遍历的解析
数据结构·c++·算法·面试·力扣·笔试·牛客网
SWAGGY..2 小时前
【c++初阶】:(1)c++入门基础知识
开发语言·c++
0 0 02 小时前
CCF-CSP 40-3 图片解码(decode)【C++】考点:矩阵翻转/旋转
开发语言·c++·矩阵
专注&突破2 小时前
DeepAgents 的 Backend详解
数据库