更新时间:2022-02-15 09:34:28 来源:极悦 浏览1345次
redis 中的事务由放置在MULTI和EXEC(或DISCARD用于回滚)之间的命令块组成。一旦MULTI 遇到 a ,该连接上的命令就不会被执行- 它们被排队(并且调用者得到QUEUED 每个的回复)。当EXEC遇到 an 时,它们都被应用在一个单元中(即没有其他连接在操作之间获得时间)。如果DISCARD看到 a 而不是 a EXEC,则所有内容都将被丢弃。因为事务内部的命令是排队的,所以你不能在事务内部做出决定 。例如,在 SQL 数据库中,您可能会执行以下操作(伪代码 - 仅用于说明):
// assign a new unique id only if they don't already
// have one, in a transaction to ensure no thread-races
var newId = CreateNewUniqueID(); // optimistic
using(var tran = conn.BeginTran())
{
var cust = GetCustomer(conn, custId, tran);
var uniqueId = cust.UniqueID;
if(uniqueId == null)
{
cust.UniqueId = newId;
SaveCustomer(conn, cust, tran);
}
tran.Complete();
}
这在 redis 事务中根本不可能:一旦事务打开,您就无法获取数据- 您的操作已排队。幸运的是,还有另外两个命令可以帮助我们:WATCH和UNWATCH.
WATCH {key}告诉 Redis 我们对指定的键感兴趣以用于事务。Redis 会自动跟踪这个键,任何更改都会使我们的事务回滚 -EXEC与DISCARD(调用者可以检测到这一点并从头开始重试)相同。所以你可以做的是:WATCH一个键,以正常方式从那个键中检查数据,然后MULTI/EXEC你的更改。如果,当您检查数据时,您发现您实际上并不需要该事务,您可以使用UNWATCH忘记所有被监视的键。请注意,监视的键也会在EXEC和期间重置DISCARD。所以在 Redis 层,这在概念上是:
WATCH {custKey}
HEXISTS {custKey} "UniqueId"
-- (check the reply, then either:)
MULTI
HSET {custKey} "UniqueId" {newId}
EXEC
-- (or, if we find there was already an unique-id:)
UNWATCH
这可能看起来很奇怪 - 有一个MULTI/EXEC只跨越一个操作 - 但重要的是我们现在也在跟踪{custKey}所有其他连接的更改 - 如果其他任何人更改密钥,事务将被中止。
StackExchange.Redis 使用多路复用器方法这一事实使情况变得更加复杂。我们不能简单地让并发调用者发出WATCH/ UNWATCH/ MULTI/ EXEC/ DISCARD:它们都会混在一起。所以提供了一个额外的抽象——另外让事情变得更简单:约束。约束基本上是预先准备好的测试,包括WATCH、某种测试和对结果的检查。如果所有约束都通过,则发出MULTI/ ;EXEC否则UNWATCH发出。这一切都是以防止命令与其他调用者混合在一起的方式完成的。所以我们的例子变成了:
var newId = CreateNewId();
var tran = db.CreateTransaction();
tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID"));
tran.HashSetAsync(custKey, "UniqueID", newId);
bool committed = tran.Execute();
// ^^^ if true: it was applied; if false: it was rolled back
请注意,从返回的对象CreateTransaction只能访问异步方法 - 因为每个操作的结果要等到Execute(或ExecuteAsync)完成之后才能知道。如果未应用操作,所有Tasks 将被标记为取消 - 否则,在命令执行后,您可以正常获取每个结果。
可用条件的集合并不广泛,但涵盖了最常见的场景;如果您想查看其他条件,请与我联系(或更好:提交拉取请求)。
还需要注意的是,Redis 已经预料到了许多常见的场景(特别是:key/hash 的存在,就像上面一样),并且存在单操作原子命令。这些是通过When参数访问的——所以我们前面的例子也可以写成:
var newId = CreateNewId();
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);
(这里是使用命令的When.NotExists原因,而不是)HSETNXHSET
您还应该记住,Redis 2.6 及更高版本支持 Lua 脚本,这是一种通用工具,用于在服务器上作为单个原子单元执行多个操作。由于在 Lua 脚本期间没有为其他连接提供服务,它的行为很像一个事务,但没有MULTI/EXEC等的复杂性。这也避免了调用者和服务器之间的带宽和延迟等问题,但权衡是它垄断了脚本期间的服务器。
在 Redis 层(假设HSETNX不存在),这可以实现为:
EVAL "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end" 1 {custKey} {newId}
这可以通过以下方式在 StackExchange.Redis 中使用:
var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
new RedisKey[] { custKey }, new RedisValue[] { newId });
(请注意,来自ScriptEvaluateand的响应ScriptEvaluateAsync是可变的,具体取决于您的确切脚本;响应可以通过强制转换来解释 - 在这种情况下为 a bool)
通过上述相信大家对Redis分布式事务已经有所了解,大家如果对此比较感性趣,想了解更多相关知识,不妨来关注一下极悦的Redis教程,里面的课程内容从浅到深,通俗易懂,适合没有基础的朋友学习,希望对大家能够有所帮助。
0基础 0学费 15天面授
Java就业班有基础 直达就业
业余时间 高薪转行
Java在职加薪班工作1~3年,加薪神器
工作3~5年,晋升架构
提交申请后,顾问老师会电话与您沟通安排学习