Skip to main content

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取消一个正在执行的事务。将事务队列清空并返回OKDISCARD

使用 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) 行为 交易。

监视 WATCHed 键以检测针对它们的更改。如果 在执行命令之前至少修改了一个监视密钥,即 整个事务中止,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 执行 仅当未修改任何 WATCHed 密钥时,事务。这包括 客户端所做的修改,如写入命令,以及 Redis 本身, 比如过期或驱逐。如果在密钥被监视和收到 EXEC 之间被修改,则整个事务将被中止 相反。

注意

  • 在 6.0.9 之前的 Redis 版本中,过期的密钥不会导致事务 要中止。更多关于这个
  • 事务中的命令不会触发 WATCH 条件,因为它们 仅在发送 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 事务,也可以使用脚本,并且 通常脚本会更简单、更快