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 修改版)。
- 发送脚本:客户端通过 EVAL 或 EVALSHA 命令将 Lua 代码发送给 Redis。
- 执行环境:Redis 主线程会暂停处理其他命令,转而执行这个 Lua 脚本。
- 沙箱环境 :为了安全,Redis 限制了 Lua 的功能。
- 禁止:不能加载外部模块、不能访问文件系统、不能执行耗时过长的操作。
- 允许:只能调用 Redis 提供的命令(通过 redis.call() 或 redis.pcall())。
- 返回结果:脚本执行完毕后,将结果返回给客户端,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;
}