redis事务

原文地址:https://redis.io/topics/transactions


前言


        本人最近在研究redis的过程中,看到国内对于redis的事务讨论激烈,但是却没有多少同志去看redis官方的事务文档,所以在此分享给大家以供参考。


正文:


事务


        MULTI,EXEC,DISCARD和WATCH是redis的事务基础。它们允许在一个步骤中执行一组命令,具有两个重要保证:

  • 事务中的所有命令都被序列化并顺序执行。在执行 Redis事务时,不会发生另一个客户端发出的请求。这确保命令作为单独的隔离操作执行。
  • 存在所有的命令都被处理或者没有被处理的命令,所以Redis事务也是有原子性的。EXEC命令会触发事务中的所有命令的执行,因此,如果客户在连接到服务器事务的上下文调用之前失去了MULTI命令执行的操作,除非执行EXEC命令,否则不会执行所有操作。当使用append-only这种持久化方式时 ,Redis确保使用单个write(2)系统调用来将事务写入磁盘。但是,如果Redis服务器崩溃或被系统管理员以某种方式杀死,可能只注册了部分操作数。Redis将在重新启动时检测到这种情况,并将会在遇到错误时退出。使用redis-check-aof工具,可以修复仅删除部分事务的附加文件,以便服务器可以重新启动。
        从版本2.2开始,Redis允许以上述两种方式提供额外的保证,以与检查(CAS)操作非常相似的方式以乐观锁定的形式。这将在本页后面介绍。


用法


        使用MULTI命令输入Redis事务。命令总是返回OK。此时用户可以发出多个命令。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 后, 命令可能会失败,例如,因为我们对一个具有错误值的键执行了一个操作(比如针对一个字符串调用一个列表操作)。
        客户端通过检查排队命令的返回值来感测在执行EXEC调用之前发生的第一种错误:如果命令用QUEUED回复,则它正确排队,否则Redis返回错误。如果在排队命令时出现错误,大多数客户端将中止丢弃该事务的事务。
但是从Redis 2.6.5开始,服务器会记住在命令累积期间发生错误,并且拒绝执行EXEC期间返回错误的事务,并自动丢弃事务。

        在Redis 2.6.5之前,行为是执行事务,只要命令队列成功排队,以防客户端调用EXEC,而不管以前的错误。新的行为使事务与流水线混合变得更加简单,以便可以一次发送整个事务,稍后再次阅读所有的回复。

        在 EXEC 之后发生的错误不是以特殊方式处理:即使某些命令在事务中失败,所有其他命令也将被执行。这在协议级别上更为明确。在以下示例中,即使语法正确,一个命令也将失败:

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

        EXEC返回两元素批量字符串回复,其中一个是OK代码,另一个是-ERR回复。客户端库可以找到向用户提供错误的合理方式。

        重要的是要注意,即使命令失败,队列中的所有其他命令都将被处理 - Redis 不会停止处理命令。
    另一个例子,再次使用有线协议telnet,显示如何以ASAP方式报告语法错误:

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

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


为什么Redis不支持回滚?


    如果您有关系数据库背景,Redis命令在事务中可能会失败,但是Redis仍将执行其余的事务而不是回滚,对您来说可能会很奇怪。

然而对于这种行为有好的观点:

  • Redis命令只能在语法错误(在命令排队期间无法检测到问题)或者持有错误数据类型的密钥进行调用时失败:这意味着在实际中,失败的命令是编程错误的结果,这种错误在开发过程中很可能被检测到,而不是在生产中。
  • Redis内部的简化和更快,因为它不需要回滚的能力。
        反对Redis观点的一个观点是错误发生,但是应该注意的是,一般来说,回滚不会使您避免编程错误。例如,如果查询将键增加2而不是1,或者递增错误的键,那么回滚机制就无法帮助。鉴于没有人可以节省程序员从他或她的错误,并且Redis命令失败所需的那种错误不太可能进入生产,我们选择了不支持回滚错误的更简单和更快捷的方法。


丢弃命令队列


    可以使用DISCARD来中止事务。在这种情况下,不执行命令,连接状态恢复正常。

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

乐观锁定使用检查和设置


    WATCH用于向Redis事务提供支票(CAS)行为。
    WATCH监视ed键以检测对它们的变化。如果在EXEC命令之前修改了至少一个被监视的密钥,则整个事务将中止,并且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

        使用上述代码,如果有竞争条件,另一个客户端修改了val在我们调用WATCH和我们对EXEC的调用之间的时间内的结果,则事务将失败。

        我们只需要重复这个操作,希望这次我们不会得到一个新的比赛。这种锁定形式称为乐观锁定,是一种非常强大的锁定形式。在许多用例中,多个客户端将访问不同的密钥,因此不太可能发生冲突 - 通常无需重复操作。


WATCH解释


        那么WATCH究竟是什么呢?这是一个使EXEC有条件的命令:我们要求Redis只有在没有WATCHed键被修改的情况下才执行该事务。(但是它们可能会被事务中的同一个客户端所改变,而不会中止它)更多关于此事情)否则,根本不输入事务。(请注意,如果您WATCH挥发性键,你的Redis到期后的关键WATCH编吧,EXEC仍然可以工作。)

        WATCH可以多次调用。所有WATCH电话都将具有观看从呼叫开始的更改的效果,直到调用EXEC。您也可以将任意数量的密钥发送到单个WATCH呼叫。

        当EXEC被调用时,所有的密钥都被UNWATCH编辑,不管事务是否中止。当客户端连接关闭时,一切都会被修改UNWATCH。也可以使用UNWATCH命令(无参数)来刷新所有被监视的键。有时,这是有用的,因为我们乐观地锁定几个键,因为可能我们需要执行一个事务来更改这些键,但是在阅读了我们不想继续的键的当前内容之后。当这种情况发生时,我们只需打电话给 UNWATCH,以便连接可以自由地用于新的事务。

     使用WATCH实现ZPOP

        说明如何使用WATCH来创建Redis不支持的新原子操作的一个很好的例子是实现ZPOP,这是一个以原子方式从排序集弹出具有较低分数的元素的命令。这是最简单的实现:
如果EXEC失败(即返回Null答复),我们只需重复该操作。

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


Redis脚本和事务


        一个Redis的脚本是定义事务性的,所以一切都可以用Redis的事务做的,你也可以做一个脚本,通常脚本会更简单,更快速。

        这种重复是由于在Redis 2.6中引入了脚本,而事务早已存在。然而,我们不太可能在短时间内消除对事务的支持,因为它似乎在语义上是适时的,即使不使用Redis脚本,仍然可以避免竞争条件,特别是因为Redis事务的实现复杂度最小。

        然而,在不久的将来我们将看到整个用户群只是使用脚本是不可能的。如果发生这种情况,我们可能会弃用并最终删除事务。




你可能感兴趣的:(redis,事务)