Redis 中的事务如何运作
Redis 事务允许执行一组命令 在一个步骤中,它们以命令MULTI,
EXEC
,DISCARD
和WATCH
为中心。 Redis 交易提供两个重要保证:
- 事务中的所有命令都序列化并执行 顺序。另一个客户端发送的请求永远不会 在执行 Redis 事务的过程中提供服务。 这保证了命令作为单个命令执行 隔离操作。
执行命令
触发事务中所有命令的执行,因此 如果客户端在 事务在调用EXEC
命令之前没有任何操作 ,而不是如果调用EXEC
命令,则所有 执行操作。使用仅追加文件时,Redis 确保 使用单个 write(2) 系统调用将事务写入磁盘。 但是,如果 Redis 服务器崩溃或被系统管理员杀死 在某些困难的方式中,可能只有部分数量的操作 已注册。Redis 将在重新启动时检测到此情况,并将退出并显示错误。 使用该工具可以修复 仅附加将删除部分事务的文件,以便 服务器可以重新启动。redis-check-aof
从版本 2.2 开始,Redis 允许对 以上两个,以乐观锁定的形式以非常类似于 检查并设置 (CAS) 操作。 本页稍后将对此进行说明。
# 用法
命令 | 作用 | 使用案例 |
---|---|---|
MULTI | 开启一个Redis事务,进入事务执行状态中 | MULTI |
WATCH | 监视一个或多个键,保证在事务执行期间这些键没有被其他客户端修改 | WATCH key1 |
EXEC | 执行事务中的所有命令,并将结果返回。若在执行事务之前,键被其他客户端所改变,则返回一个空结果数组 | EXEC |
DISCARD | 取消一个正在执行的事务。将事务队列清空并返回OK | DISCARD |
使用 MULTI
命令输入 Redis 事务。命令 始终使用 回复 。此时,用户可以发出多个 命令。Redis 将排队,而不是执行这些命令 他们。调用 EXEC
后,将执行所有命令。OK
改为调用 DISCARD
将刷新事务队列并退出 事务。
以下示例以原子方式递增键。foo``bar
> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
从上面的会话中可以清楚地看出,EXEC
返回一个 回复数组,其中每个元素都是单个命令的回复 在事务中,命令的发出顺序相同。
当 Redis 连接位于 MULTI
请求的上下文中时, 所有命令都将回复字符串(作为状态回复发送) 从 Redis 协议的角度来看)。排队的命令是 只是安排在调用 EXEC
时执行。QUEUED
# 事务中的错误
redis 单条命令保证原子性 但是事务不保证原子性
在事务过程中,可能会遇到两种命令错误:
- 命令排队失败(命令语法发生错误),因此在调用
EXEC
之前可能会出现错误。 例如,命令可能在语法上错误(参数数量错误, 错误的命令名称,...),或者可能存在一些关键情况,例如 内存条件(如果使用指令将服务器配置为具有内存限制)。maxmemory
- 调用
EXEC
后命令可能会失败(运行命令后失败),例如,因为我们执行了 针对具有错误值的键的操作(如对字符串值调用列表操作)。
从 Redis 2.6.5 开始,服务器将在命令累积过程中检测到错误。 然后,它将拒绝执行在 EXEC
期间返回错误的事务,从而丢弃该事务。
Redis < 2.6.5 的注意事项:在 Redis 2.6.5 之前,客户端需要通过检查来检测
在 EXEC
之前发生的错误 排队命令的返回值:如果命令回复 QUEUED,则为 正确排队,否则 Redis 将返回错误。 如果在对命令进行排队时出现错误,则大多数客户端 将中止并丢弃事务。否则,如果客户选择继续交易EXEC
命令将成功执行所有排队的命令,而不考虑以前的错误。
相反,在 EXEC
之后发生的错误不会以特殊方式处理: 即使某些命令在事务期间失败,也将执行所有其他命令。
回滚
Redis 不支持事务回滚,因为支持回滚 将对 Redis 的简单性和性能产生重大影响。但是可以运用其他技术栈达到回滚的功能
放弃命令队列
可以使用 DISCARD
来中止事务。在这种情况下,没有 执行命令并将连接状态还原为 正常。
> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"
使用检查和设置的乐观锁定
WATCH
用于向 Redis 提供检查和设置 (CAS) 行为 交易。
监视 WATCH
ed 键以检测针对它们的更改。如果 在执行
命令之前至少修改了一个监视密钥,即 整个事务中止,EXEC
返回 Null 回复以通知 事务失败。
例如,假设我们需要原子地递增值。 键的 1(假设 Redis 没有 INCR
)。
第一次尝试可能如下:
val = GET mykey
val = val + 1
SET mykey $val
只有当我们有一个客户端执行 在给定时间内的操作。如果多个客户端尝试递增密钥 大约在同一时间,将有一个竞争条件。例如 客户端 A 和 B 将读取旧值,例如 10。该值将 由两个客户端递增到 11,最后 SET
作为值 的键。因此,最终值将是 11 而不是 12。
多亏了WATCH
,我们能够很好地对问题进行建模:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的代码,如果有竞争条件和另一个客户端 修改我们调用 WATCH
和 我们对 EXEC
的调用,事务将失败。val
我们只需要重复操作,希望这次我们不会得到 新种族。这种形式的锁定称为乐观锁定。 在许多用例中,多个客户端将访问不同的密钥, 因此不太可能发生碰撞 - 通常无需重复操作。
WATCH解释
那么,WATCH
到底是什么?这是一个命令,将 使 EXEC
有条件:我们要求 Redis 执行 仅当未修改任何 WATCH
ed 密钥时,事务。这包括 客户端所做的修改,如写入命令,以及 Redis 本身, 比如过期或驱逐。如果在密钥被监视
和收到 EXEC
之间被修改,则整个事务将被中止 相反。
注意
可以
多次调用 WATCH。简单地说,所有的观察
呼叫都会 具有监视从通话开始的变化的效果,直到 执行官
被召唤的那一刻。您还可以将任意数量的密钥发送到 单个监视
呼叫。
调用 EXEC
时,所有密钥都将取消监视
,无论是否 事务是否已中止。此外,当客户端连接 关闭,一切都被取消监视
。
也可以使用 UNWATCH
命令(不带参数) 为了刷新所有监视的键。有时这很有用,因为我们 乐观地锁定几个键,因为可能需要执行 事务以更改这些密钥,但在读取当前内容后 我们不想继续的键。发生这种情况时,我们只需调用UNWATCH
,以便连接已经可以自由用于新的 交易。
使用 WATCH 实现 ZPOP
一个很好的例子来说明如何使用 WATCH
来创建新的 Redis 不支持的原子操作是实现 ZPOP (ZPOPMIN
,ZPOPMAX
及其阻塞变体仅添加 在 5.0 版中),这是一个弹出具有较低元素的命令 以原子方式从排序集合中得分。这是最简单的 实现:
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
如果 EXEC
失败(即返回 Null 回复),我们只需重复该操作。
Redis 脚本和事务
对于 redis 中的事务操作,需要考虑的其他事项是事务性的 redis 脚本。万事 您可以使用 Redis 事务,也可以使用脚本,并且 通常脚本会更简单、更快