Redis Transactions

来源:互联网 发布:长春知远画室怎么样 编辑:程序博客网 时间:2024/05/22 01:48

Redis Transactions

MULTI、EXEC、DISCARD、WATCH命令是Redis事务的基础,它们允许一组命令单一执行,保证:

  • 一个事务中的所有命令都是串行的,并且是连续的执行。在Redis一个事务执行过程中一定不会受到别一个客户端请求的影响,保证事务中的一组命令执行为隔离操作。
  • 一个事务中的所有命令要么都执行成功,要么就一个都不执行,所以Redis事务是自动事务。

Usage

进入Redis事务使用MULTI命令。此命令总是返回“OK”,此时用户可以输入多个命令,这些命令并不马上执行,而是被Redis放入到队列中,调用EXEC命令,所有命令将被执行,调用DISCARD命令则将清除事务队列并退出事务。

下面是一个事务的简单示例:

>MULTIOK>INCR barQUEUED>INCR fooQUEUED>EXEC1)(integer) 12) (integer) 1

注:正如上面看到的,EXEC命令返回的是一个数组,数组中每个元素都是事务中单个命令的返回值,顺序也同命令顺序一样。

Errors inside a transaction:事务中的错误

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

  • 命令放入事务队列失败。此错误发生在EXEC命令被调用之前。例如,命令语法错误(参数个数错误,命令名称错误等),或者是因为一些限制条件就像超出内存限制条件(如果服务器使用maxmemory配置了最大内存限制)。
  • 命令在调用EXEC命令后执行失败。例如因为执行在key上放错误类型的value(就像调用操作List的操作操作字符串)。

客户端通常可能看出发生在调用EXEC命令之前的上述第一种错误,查看MULTI命令后EXEC命令前的其他命令返回值即可:如果返回状态为QUEUED则表示加入事务队列成功了,否则Redis会返回一个错误。

从Redis 2.6.5版本开始,开启事务后,如果客户端发生了上述第一种错误,那么服务器也会记下此事务错误并且拒绝执行事务中的命令,还会在EXEC命令期间返回一个错误并且自动清除事务。

在2.6.5版本之前则是调用EXEC命令时忽略这些错误,只执行加入事务队列成功的命令。

错误发生在EXEC之后的第二种错误则是不可掌控的,Redis对此则是:事务中的所有命令都会被执行,即使一些命令在事务期间失败了。

下面是一个语法正确但执行时会失败的例子:

Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.MULTI+OKSET a 3abc+QUEUEDLPOP A+QUEUEDEXEC*2+OK-ERR Operation against key holding the wrong kind of value

有一点很重要,那就是即使事务中某条命令执行失败了,其他命令还是会执行,Redis不会停止其他命令的执行。

下面是一个使用telnet协议,展示语法错误时报告的错误消息:

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

此时因为语法错误INCR命令就不能添加到事务队列中去。

为什么Redis不支持事务回滚

如果你对关系型数据库有一定了解,那么对于Redis在事务期间命令执行失败继续执行其他命令的行为你可能会感到很奇怪,但是,Redis使用此种行为也有两大理由:

  • Redis命令仅会在调用语法错误(此种错误在命令加入队列时是不可检测的),或者对key进行错误数据类型处理时失败。也就是说实际生产中Redis命令执行错误是程序错误引起的,并且这此错误完全是可以开发期间检查出来的。
  • Redis因为不需要回滚功能而内部简单且更快。

清除命令队列

离开一个事务可以使用DISCARD命令,这种情况下。命令不会执行并且连接被重置为正常连接。

>SET foo 1OK>MULTIOK>INCR FOOQUEUED>DISCARDOK>GET foo"1"

使用check-and-set的乐观锁

WATCH命令用于给Redis事务提供check-and-set行为。

被“WATCH”的keys处于被监控状态,以检查它们的改变。如果在调用EXEC命令之前至少有一个被WATCH的key被改变,整个事物就会终止,并且EXEC命令返回一空的回复来通知事物的失败。例如,假设你需要自动给一个key的值加1(让我们假设Redis没有INCR命令),那么首先的尝试可以如下:

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

仅仅当我们在给定时间内只有一个客户端执行此操作的时候这种实现在靠谱,如果同一时间多个客户端同时试图给key加1,那么就会形成竞争条件。例如,客户端A和客户端B同时读取key的老值,假设为10,那么此key的值两个客户端都会增加到11,并且最后给key设值时设置的也是11,而不是12。

幸好WATCH命令可以用来解决这个问题:

WATCH mykeyval = GET mykeyval = val + 1MULTISET mykey $valEXEC

使用上面的代码,如果存在竞争条件并且另一个客户端在我们调用WATCH命到调用EXEC命令期间改变了要自增的值,事物就会失败。

我们仅仅需要重复这个操作,希望这次不会再出现竞争。这种形式的锁被叫作乐观锁,是一种非常强大形式的锁。在许多使用场景中,多个客户端往往访问不同的keys,所以冲突不太可能,所以通常不需要重复我们的操作。

WATCH explained–WATCH命令阐述

WATCH命令到底是干什么的呢?它是一个可以让EXEC命令视条件而定的命令:仅仅当没有被WATCHED的keys改变时,我们请求Redis执行事务(事务内部对被WATCHED的key改变同样不会终止事务),否则终止事务。

WATCH命令可以被多次调用。简单的说,从调用开始,所有的WATCH命令调用对于监控对象变更有效,到EXEC命令被调用那一刻结束。你也可以使用一个WATCH命令监控多个keys。

当EXEC命令被调用,不管事务是否终止,所有的keys的监控被取消,或者客户端连接关闭,同样keys监控取消。

也可以通过UNWATCH命令(无参)来清除所的keys监控。某些时候,对少量keys加乐观锁是非常有用的,因为我们可能需要执行事务来修改那些keys,但是我们可能在读取被监控key内容后我们可以并不想操作它。当这种情况发生时,我们仅仅需要调用UNWATCH命令来清空监控,以便连接能够进行新的事务。

使用WATCH命令实现ZPOP命令
一个较好的例子用于说明WATCH命令怎么被用于创建新的原子操作或者说不被Redis本身支持的原子操作就是实现ZPOP命令。ZPOP命令就是用于弹出Sorted-set中score更低元素的原子操作,简单实现如下:

WATCH zsetelement = ZRANGE zset 0 0MULTIZREM zset elementEXEC

如果EXEC命令执行失败,我们只需要重复执行此操作即可。

Redis scripting and transactions

redis脚本是事务式的,所以只要能用Redis事务干的事,都可以通过脚本干,并且通常情况下,使用脚本既简单又速度。

这种重复是因为使用脚本是从Redis 2.6开始的,那时候事务已经存在好久了。然而我们不便于在短时间内删除对事务的支持是因为从语义上说即使不使用Redis脚本来避免竞争条件是适当的,还有就是Redis事务的实现复杂度是最小的。

但是,也许在不远的将来我们会发现基本所有的用户都在使用脚本,那个时候,我们可能会最终删除对事务的支持。

原创粉丝点击