一、Redis 事务核心原理(C++ 视角)
Redis 事务通过 MULTI(开启事务)→ 执行多个命令(入队)→ EXEC(提交事务)/DISCARD(取消事务)完成,hiredis 库中需按此流程依次发送命令,且所有入队命令仅在 EXEC 后执行。
WATCH:可选,监控指定 key,若事务执行前 key 被修改,事务会被取消(乐观锁);MULTI:标记事务开始,后续命令进入队列而非立即执行;EXEC:执行队列中的所有命令,返回所有命令结果;DISCARD:清空队列,取消事务。
二、C++ 实现 Redis 事务的完整代码
以下示例包含基础事务、带 WATCH 的事务、取消事务三种场景,基于 hiredis 库实现:
cpp
运行
#include <iostream>
#include <string>
#include <hiredis/hiredis.h>
using namespace std;
// 连接Redis(复用之前的连接函数)
redisContext* connectRedis(const string& ip, int port, const string& password = "") {
struct timeval timeout = {10, 0};
redisContext* ctx = redisConnectWithTimeout(ip.c_str(), port, timeout);
if (ctx == nullptr || ctx->err) {
if (ctx) {
cerr << "连接失败:" << ctx->errstr << endl;
redisFree(ctx);
} else {
cerr << "连接对象创建失败!" << endl;
}
return nullptr;
}
// 密码认证
if (!password.empty()) {
redisReply* reply = (redisReply*)redisCommand(ctx, "AUTH %s", password.c_str());
if (reply == nullptr || reply->type == REDIS_REPLY_ERROR) {
cerr << "认证失败:" << (reply ? reply->str : "无返回") << endl;
freeReplyObject(reply);
redisFree(ctx);
return nullptr;
}
freeReplyObject(reply);
}
return ctx;
}
// 执行单个Redis命令(辅助函数)
redisReply* execCmd(redisContext* ctx, const char* cmd, ...) {
if (!ctx) return nullptr;
va_list args;
va_start(args, cmd);
redisReply* reply = (redisReply*)redisvCommand(ctx, cmd, args);
va_end(args);
if (reply && reply->type == REDIS_REPLY_ERROR) {
cerr << "命令错误:" << reply->str << endl;
}
return reply;
}
int main() {
// 1. 连接Redis(替换为你的配置)
redisContext* ctx = connectRedis("127.0.0.1", 6379, "");
if (!ctx) return -1;
// ==================== 场景1:基础事务(MULTI + EXEC) ====================
cout << "===== 基础事务 =====" << endl;
// 1.1 开启事务
redisReply* reply = execCmd(ctx, "MULTI");
if (reply && reply->str == string("OK")) {
cout << "事务已开启" << endl;
}
freeReplyObject(reply);
// 1.2 入队命令(仅入队,不执行)
reply = execCmd(ctx, "SET user:1:name Alice"); // 命令1
freeReplyObject(reply);
reply = execCmd(ctx, "HSET user:1 age 25 gender female"); // 命令2
freeReplyObject(reply);
reply = execCmd(ctx, "GET user:1:name"); // 命令3
freeReplyObject(reply);
// 1.3 提交事务(执行所有入队命令)
reply = execCmd(ctx, "EXEC");
if (reply && reply->type == REDIS_REPLY_ARRAY) {
cout << "事务执行结果:" << endl;
// 遍历EXEC返回的数组(对应入队命令的结果)
for (size_t i = 0; i < reply->elements; i++) {
redisReply* elem = reply->element[i];
if (elem->type == REDIS_REPLY_STATUS) {
cout << " 命令" << i+1 << ":" << elem->str << endl;
} else if (elem->type == REDIS_REPLY_STRING) {
cout << " 命令" << i+1 << ":" << elem->str << endl;
} else if (elem->type == REDIS_REPLY_INTEGER) {
cout << " 命令" << i+1 << ":" << elem->integer << endl;
}
}
}
freeReplyObject(reply);
// ==================== 场景2:带WATCH的事务(乐观锁) ====================
cout << "\n===== 带WATCH的事务 =====" << endl;
// 2.1 先设置一个初始值
reply = execCmd(ctx, "SET balance 100");
freeReplyObject(reply);
// 2.2 监控balance键(乐观锁:若该键被修改,事务取消)
reply = execCmd(ctx, "WATCH balance");
freeReplyObject(reply);
// 2.3 开启事务,入队扣减余额命令
reply = execCmd(ctx, "MULTI");
freeReplyObject(reply);
reply = execCmd(ctx, "DECRBY balance 50"); // 扣减50
freeReplyObject(reply);
// 【模拟其他客户端修改balance(触发WATCH)】
// 注释掉这行,事务会成功;放开注释,事务会返回空(执行失败)
// reply = execCmd(ctx, "SET balance 200"); // 外部修改WATCH的key
// freeReplyObject(reply);
// 2.4 提交事务
reply = execCmd(ctx, "EXEC");
if (reply == nullptr) {
cout << "事务执行失败(连接异常)" << endl;
} else if (reply->type == REDIS_REPLY_NIL) {
// WATCH的key被修改,事务取消,EXEC返回nil
cout << "事务取消(WATCH的key被修改)" << endl;
} else if (reply->type == REDIS_REPLY_ARRAY) {
cout << "事务成功,扣减后余额:" << reply->element[0]->integer << endl;
}
freeReplyObject(reply);
// ==================== 场景3:取消事务(DISCARD) ====================
cout << "\n===== 取消事务 =====" << endl;
reply = execCmd(ctx, "MULTI");
freeReplyObject(reply);
reply = execCmd(ctx, "SET temp key123"); // 入队一个命令
freeReplyObject(reply);
// 取消事务(清空队列)
reply = execCmd(ctx, "DISCARD");
if (reply && reply->str == string("OK")) {
cout << "事务已取消" << endl;
}
freeReplyObject(reply);
// 验证temp键是否存在(应为不存在)
reply = execCmd(ctx, "GET temp");
if (reply->type == REDIS_REPLY_NIL) {
cout << "temp键未被设置(事务取消生效)" << endl;
}
freeReplyObject(reply);
// 断开连接
redisFree(ctx);
return 0;
}
三、核心代码解释
-
事务流程关键步骤:
WATCH balance:监控balance键,若事务执行前该键被其他客户端修改,EXEC会返回nil,事务取消;MULTI:开启事务后,后续SET/HSET/DECRBY等命令仅入队,不会立即执行;EXEC:提交事务,Redis 批量执行队列中的所有命令,返回一个数组(每个元素对应入队命令的结果);DISCARD:清空命令队列,取消当前事务,不会执行任何入队命令。
-
EXEC 返回值处理:
- 事务成功:返回
REDIS_REPLY_ARRAY类型,数组长度等于入队命令数,每个元素对应命令的执行结果; - 事务取消(WATCH 触发):返回
REDIS_REPLY_NIL; - 命令错误:入队时语法错误会直接返回错误,但不影响其他命令入队;运行时错误(如对字符串执行 HSET)仅该命令失败,其他命令仍执行(Redis 事务不支持回滚)。
- 事务成功:返回
四、Redis 事务的注意事项(C++ 开发重点)
- 无回滚机制:Redis 事务中某条命令执行失败(如类型错误),其他命令仍会执行,需在代码中自行校验命令合法性;
- WATCH 仅一次有效 :
EXEC/DISCARD后,WATCH会自动取消,若需再次监控需重新执行WATCH; - 资源释放 :每个
redisReply都需调用freeReplyObject释放,避免内存泄漏; - 连接异常处理:事务执行中若连接断开,队列中的命令不会执行,需在代码中增加重连和重试逻辑;
- 进阶替代方案 :若需强事务支持(如回滚),可使用 Redis 6.0 + 的
CLUSTER事务或 Lua 脚本(EVAL),Lua 脚本会原子性执行所有命令,且支持条件判断。
总结
- C++ 中使用 Redis 事务的核心流程是:
WATCH(可选) → MULTI → 入队命令 → EXEC/DISCARD; EXEC的返回值是数组类型,需遍历解析每个命令的结果,WATCH触发时返回nil;- Redis 事务无回滚机制,需注意命令合法性校验,且
WATCH仅提供乐观锁能力。