专注Java教育14年 全国咨询/投诉热线:444-1124-454
赢咖4LOGO图
始于2009,口口相传的Java黄埔军校
首页 hot资讯 Redis分布式事务

Redis分布式事务

更新时间:2022-02-15 09:34:28 来源:赢咖4 浏览1252次

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 中做到这一点呢?

这在 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 中呢?

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 将被标记为取消 - 否则,在命令执行后,您可以正常获取每个结果。

可用条件的集合并不广泛,但涵盖了最常见的场景;如果您想查看其他条件,请与我联系(或更好:提交拉取请求)。

内置操作通过When

还需要注意的是,Redis 已经预料到了许多常见的场景(特别是:key/hash 的存在,就像上面一样),并且存在单操作原子命令。这些是通过When参数访问的——所以我们前面的例子也可以写成:

var newId = CreateNewId();
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);

(这里是使用命令的When.NotExists原因,而不是)HSETNXHSET

Lua

您还应该记住,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分布式事务已经有所了解,大家如果对此比较感性趣,想了解更多相关知识,不妨来关注一下赢咖4的Redis教程,里面的课程内容从浅到深,通俗易懂,适合没有基础的朋友学习,希望对大家能够有所帮助。

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>