StackExchange.Redis 系列 5:事务
本系列博文是“伪”官方文档翻译,并非完全将官方文档进行翻译,而是我在查阅、测试原始文档并转换为自己东西后进行的“准”翻译。
本系列本博文基于 redis 5.0.6,系列中部分博文跟官方文档有出入,有不同见解 / 说明不当的地方,还请大家不吝拍砖。
Redis 中的事务说明
-
Redis 中的事务跟我们常说的数据库事务不同:
- 数据库事务必须保证全部成功,否则就回滚。
- 而 Redis 中的事务更偏向于“一组打包的批量执行脚本”,其中任何一条命令的执行失败,不会导致已经执行的命令回滚,也不会中断后续的命令执行。
-
在使用数据库事务的时候,你可以在事务中使用条件判断。但在 Redis 事务中,你无法使用条件判断。(条件见下方说明)
如何使用事务?
-
在 Redis 原生命令中使用 MULTI 来开启事务,EXEC 来执行事务(或者 DISCARD 来取消事务)
-
一旦使用 MULTI,在 MULTI 之后的命令不会立即执行,它们会排队,直到接收到 EXEC命令。如果接收到的时 DISCARD 命令,则所有已传输的命令会全部抛弃。
- 因为 Redis 命令会排队执行,所以无法在 Redis 中使用条件判断。
是否有方法可以在 redis 事务中使用条件判断?
答案是肯定的,可以通过使用 WATCH 和 UNWATCH 命令来实现 redis 中使用条件判断。
-
WATCH
:监听一个 key,当这个 key 有任何变动,都会导致事务的回滚
使用 Redis 原生命令来使用事务
1 | WATCH {custKey} |
使用 StackExchange.Redis 来使用事务。
-
StackExchange.Redis 通过 Multiplexer 方法来实现事务。
1 | var newId = CreateNewId(); |
-
当所有限制条件都通过的时候,才会执行 EXEC,否则会使用 DISCARD 进行回滚。
-
需要注意的是只有在异步方法中才能调用 “db.CreateTransaction()”方法,同步方法中无法点出该方法。
-
当事务成功执行,则正常获取结果,否则,所有 Tasks 都将会 cancelled。
内置命令 When 说明
-
判断是否存在一个对象/不存在一个对象,这样的使用场景还是比较普遍的,因此 StackExchange.Redis 内置了一个 When 枚举参数来简化这种操作。
-
使用 When 来简化上方的业务代码:
1 | var newId = CreateNewId(); |
-
这里的 When.NotExists 会触发 SETNX 命令而非 HSET 命令。
LUA
-
Redis 2.6 版本开始,提供了 LUA 脚本。因为 LUA 脚本在执行的时候会独占服务器,所以通过 LUA 脚本,可以更加语义化的实现"事务"。
-
相对的,在执行 LUA 脚本的时候,整个服务器会被独占,导致其他请求等待。
1 | 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} |
上述 Redis 原生命令可以用以下 StackExchange.Redis 代码来实现:
1 | 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 }); |
-
需要注意的是,返回数据取决于你的脚本内容,你需要对结果按需进行转换。如上方例子,返回结果是 Boolean 类型