> 文章列表 > Redis基础教程—事务(官方原版)

Redis基础教程—事务(官方原版)

Redis基础教程—事务(官方原版)

Redis事务允许在一个步骤中执行一组命令,它们以命令MULTI、EXEC、DISCARD和WATCH为中心。Redis交易提供两个重要保证:

  • 事务中的所有命令都是按顺序序列化和执行的。在Redis事务执行过程中,另一个客户端发送的请求将永远不会得到服务。这保证了命令作为一个单独的隔离操作来执行。
  • EXEC命令会触发事务中所有命令的执行,因此,如果客户端在调用EXEC命令之前在事务上下文中失去了与服务器的连接,则不会执行任何操作,相反,如果调用了EXEC命令,则会执行所有操作。当使用仅追加文件时,Redis确保使用单个write(2)系统调用将事务写入磁盘。但是,如果Redis服务器崩溃或被系统管理员以某种艰难的方式终止,则可能只注册了部分操作。Redis将在重新启动时检测到这种情况,并将退出并返回错误。使用redis check aof工具,可以修复将删除部分事务的仅追加文件,以便服务器可以重新启动。

从2.2版开始,Redis允许对上述两个操作提供额外的保证,其形式是乐观锁定,与检查和设置(CAS)操作非常相似。本页稍后将对此进行记录。

一、用法

使用MULTI命令输入Redis事务。命令总是以“确定”作为回复。此时用户可以发出多个命令。Redis将不执行这些命令,而是对它们进行排队。调用EXEC后,将执行所有命令。
调用DISCARD将刷新事务队列并退出事务。
以下示例以原子方式递增键foo和bar:

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

从上面的会话中可以清楚地看出,EXEC返回一个应答数组,其中每个元素都是事务中单个命令的应答,其顺序与发出命令的顺序相同。
当Redis连接处于MULTI请求的上下文中时,所有命令都将使用字符串QUEUED进行回复(从Redis协议的角度来看,作为状态回复发送)。排队的命令只是在调用EXEC时安排执行。

二、事务中的错误

在事务过程中,可能会遇到两种命令错误:

  • 命令可能无法排队,因此在调用EXEC之前可能会出现错误。例如,该命令可能在语法上错误(参数数量错误、命令名称错误…),或者可能存在一些关键条件,如内存不足(如果使用maxmemory指令将服务器配置为具有内存限制)。
  • 调用EXEC后,命令可能会失败,例如,因为我们对具有错误值的键执行了操作(如对字符串值调用列表操作)。

从 Redis 2.6.5 开始,服务器将在命令累积过程中检测到错误。 然后,它将拒绝执行在EXEC 期间返回错误的事务,从而丢弃该事务。

相反,在 EXEC 之后发生的错误不会以特殊方式处理: 即使某些命令在事务期间失败,也将执行所有其他命令。

这在协议级别上更为明确。在下面的示例中,一个 命令在执行时将失败,即使语法正确:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MULTI
+OK
SET a abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-ERR Operation against a key holding the wrong kind of value

EXEC返回了两个元素的大容量字符串应答,其中一个是OK代码,另一个是-ERR应答。这取决于客户端库找到一种合理的方式来向用户提供错误。
需要注意的是,即使一个命令失败,队列中的所有其他命令都会被处理——Redis不会停止对命令的处理。
另一个例子,再次使用telnet的有线协议,显示了如何尽快报告语法错误:

MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command

这一次,由于语法错误,错误的INCR命令根本没有排队。

三、关于回滚

Redis不支持事务回滚,因为支持回滚会对Redis的简单性和性能产生重大影响。

1、放弃命令队列

DISCARD可用于中止事务。在这种情况下,不执行任何命令,并且连接状态恢复到正常:

> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"

2、使用检查和设置的乐观锁定

WATCH用于为Redis事务提供检查和设置(CAS)行为。
监视被监视的钥匙,以便检测它们的变化。如果在执行EXEC命令之前至少修改了一个被监视的密钥,则整个事务将中止,EXEC将返回一个Null回复以通知事务失败。
例如,假设我们需要将键的值原子递增1(假设Redis没有INCR)。
第一次尝试可能如下:

val = GET mykey
val = val + 1
SET mykey $val

只有当我们有一个客户端在给定的时间内执行操作时,这才能可靠地工作。如果多个客户端试图在大约相同的时间增加密钥,则会出现竞争条件。例如,客户端A和B将读取旧值,例如10。该值将由两个客户端递增到11,最后设置为密钥的值。因此,最终值将是11,而不是12。
多亏了WATCH,我们能够很好地模拟问题:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上面的代码,如果存在竞争条件,并且另一个客户端在我们调用WATCH和调用EXEC之间的时间内修改了val的结果,则事务将失败。
我们只需要重复操作,希望这次不会有新的比赛。这种形式的锁定称为乐观锁定。在许多用例中,多个客户端将访问不同的密钥,因此不太可能发生冲突——通常不需要重复操作。

4、WATCH解释

那么WATCH到底是关于什么的呢?这是一个将EXEC设为条件的命令:我们要求Redis仅在没有修改WATCHed密钥的情况下执行事务。这包括客户端所做的修改,如写入命令,以及Redis本身所做的更改,如过期或驱逐。如果密钥在WATCHed和接收到EXEC之间被修改,则整个事务将被中止。

在6.0.9之前的Redis版本中,过期的密钥不会导致事务中止。关于此的更多信息
事务中的命令不会触发WATCH条件,因为它们只在发送EXEC之前排队。

WATCH可以调用多次。简单地说,从调用开始到调用EXEC,所有WATCH调用都将具有监视更改的效果。您还可以向单个WATCH呼叫发送任意数量的密钥。
当调用EXEC时,无论事务是否中止,所有密钥都将被UNWATCH。此外,当客户端连接关闭时,所有内容都将被解除监视。

也可以使用UNWATCH命令(不带参数)来刷新所有被监视的密钥。有时这很有用,因为我们乐观地锁定了一些密钥,因为我们可能需要执行事务来更改这些密钥,但在读取了密钥的当前内容后,我们不想继续。当这种情况发生时,我们只需调用UNWATCH,这样连接就可以自由用于新的事务了。

5、使用 WATCH 实现 ZPOP

一个很好的例子来说明如何使用 WATCH 来创建新的 Redis 不支持的原子操作是实现 ZPOP (ZPOPMIN,ZPOPMAX及其阻塞变体仅添加 在 5.0 版中),这是一个弹出具有较低元素的命令 以原子方式从排序集合中得分。这是最简单的 实现:

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

如果 EXEC 失败(即返回 Null 回复),我们只需重复该操作。

 

Jedis中的事务处理:Jedis 使用详解(官方原版)

 大家好,我是Doker品牌的Sinbad,欢迎点赞和评论,您的鼓励是我们持续更新的动力!欢迎加微信进入技术群聊!