WATCH
的键。Redis支持执行Lua脚本,脚本中的操作是原子性的,且不需要WATCH
机制。通过将事务逻辑封装在Lua脚本中,可以避免事务冲突和重试的开销。
优点:
WATCH
。示例:
local key = KEYS[1]
local value = redis.call('GET', key)
if not value then
value = 0
end
redis.call('SET', key, value + 1)
return value + 1
在C++中调用Lua脚本:
redisReply* reply = (redisReply*)redisCommand(context, "EVAL %s 1 mykey", luaScript);
if (reply) {
printf("Result: %s\n", reply->str);
freeReplyObject(reply);
}
如果必须使用WATCH
,可以通过引入版本号或时间戳来实现乐观锁,减少冲突概率。
实现方式:
示例:
void optimisticTransaction(redisContext* context, const std::string& key) {
int retries = 3;
while (retries--) {
redisReply* reply = (redisReply*)redisCommand(context, "WATCH %s", key.c_str());
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "GET %s", key.c_str());
int version = reply ? atoi(reply->str) : 0;
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "MULTI");
freeReplyObject(reply);
// 添加事务操作
reply = (redisReply*)redisCommand(context, "INCR %s", key.c_str());
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "EXEC");
if (reply != nullptr) {
// 事务成功
freeReplyObject(reply);
break;
} else {
// 事务失败,重试
freeReplyObject(reply);
std::this_thread::sleep_for(std::chrono::milliseconds(100 * (3 - retries))); // 指数退避
}
}
}
如果事务中的操作是独立的,可以将多个操作合并为一个批量操作,减少事务的使用频率。
示例:
MGET
、MSET
等批量命令。如果事务冲突非常频繁,可以考虑使用分布式锁(如Redlock)来保护关键资源,确保同一时间只有一个客户端可以修改数据。
注意:
INFO
命令)观察事务冲突的频率,定位热点键。通过以上方法,可以有效减少因WATCH
失败导致的事务重试开销,提升系统性能。
UNWATCH
是 Redis 中与 WATCH
配合使用的命令,用于取消对所有键的监控。理解 UNWATCH
的应用场景需要先明确 WATCH
的作用。
WATCH
的作用WATCH
用于监控一个或多个键,如果在事务执行期间(即 MULTI
和 EXEC
之间),这些键被其他客户端修改,当前客户端的事务会失败(EXEC
返回 nil
)。WATCH
提供了一种乐观锁机制,确保事务的原子性。UNWATCH
的作用UNWATCH
用于取消对所有键的监控。UNWATCH
后,之前通过 WATCH
监控的键将不再被监控,即使这些键被其他客户端修改,也不会影响当前客户端的事务。UNWATCH
的应用场景在某些场景下,客户端可能希望显式地取消对键的监控,而不是等待事务执行完成(EXEC
或 DISCARD
)后自动取消监控。
示例场景:
UNWATCH
取消监控,避免不必要的资源占用。代码示例:
redisReply* reply = (redisReply*)redisCommand(context, "WATCH mykey");
freeReplyObject(reply);
// 检查某些条件
if (someConditionIsNotMet) {
// 取消监控
reply = (redisReply*)redisCommand(context, "UNWATCH");
freeReplyObject(reply);
return;
}
// 执行事务
reply = (redisCommand(context, "MULTI"));
freeReplyObject(reply);
reply = (redisCommand(context, "INCR mykey"));
freeReplyObject(reply);
reply = (redisCommand(context, "EXEC"));
freeReplyObject(reply);
当事务因 WATCH
的键被修改而失败时,Redis 会自动取消对所有键的监控。但在某些情况下,客户端可能希望显式调用 UNWATCH
来确保状态清理。
示例场景:
UNWATCH
以确保没有残留的监控状态。代码示例:
redisReply* reply = (redisReply*)redisCommand(context, "WATCH mykey");
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "MULTI");
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "INCR mykey");
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "EXEC");
if (reply == nullptr) {
// 事务失败,显式取消监控
reply = (redisReply*)redisCommand(context, "UNWATCH");
freeReplyObject(reply);
} else {
// 事务成功
freeReplyObject(reply);
}
在长生命周期的连接中(例如连接池中的连接),客户端可能需要多次使用 WATCH
和事务。为了避免前一次的监控状态影响后续操作,可以在每次事务结束后显式调用 UNWATCH
。
示例场景:
UNWATCH
。代码示例:
void executeTask(redisContext* context, const std::string& key) {
// 监控键
redisReply* reply = (redisReply*)redisCommand(context, "WATCH %s", key.c_str());
freeReplyObject(reply);
// 执行事务
reply = (redisReply*)redisCommand(context, "MULTI");
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "INCR %s", key.c_str());
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "EXEC");
if (reply == nullptr) {
// 事务失败
freeReplyObject(reply);
} else {
// 事务成功
freeReplyObject(reply);
}
// 显式取消监控,确保连接可以安全地用于其他任务
reply = (redisReply*)redisCommand(context, "UNWATCH");
freeReplyObject(reply);
}
在某些复杂的业务逻辑中,客户端可能会在多个地方使用 WATCH
,如果不及时清理监控状态,可能会导致意外的行为。
示例场景:
WATCH
,但某些函数可能没有执行事务。UNWATCH
。代码示例:
void functionA(redisContext* context) {
redisReply* reply = (redisReply*)redisCommand(context, "WATCH key1");
freeReplyObject(reply);
// 某些逻辑
if (someCondition) {
return; // 提前返回
}
// 显式取消监控
reply = (redisReply*)redisCommand(context, "UNWATCH");
freeReplyObject(reply);
}
void functionB(redisContext* context) {
redisReply* reply = (redisReply*)redisCommand(context, "WATCH key2");
freeReplyObject(reply);
// 某些逻辑
if (someCondition) {
return; // 提前返回
}
// 显式取消监控
reply = (redisReply*)redisCommand(context, "UNWATCH");
freeReplyObject(reply);
}
UNWATCH
的主要应用场景包括:
在复杂的业务逻辑中,合理使用 UNWATCH
可以确保 Redis 连接的状态清晰,避免意外的行为。