《Redis官方文档》事务

原文链接

事务

MULTIEXECDISCARDWATCH 是 Redis 事务的基础。事务允许一次独立的执行一组命令,并且拥有两个重要的保证。

  • Redis事务的执行是单步的独立的操作:所有的在事务中的命令都是序列化和顺序地。它在执行事务中永远不会被另一个客户端打断。
  • Redis的事务是原子性的:所有的命令,要么全部执行,要么全部不执行。
    • EXEC的命令触发执行Redis事务中的所有命令,所以如果这个客户端之前调用了MULTI命令却断开了redis事务中的连接,那么这个事务的将不会被执行。
    • 当我们使用了AOF序列化(append-only-file)时,Redis会确保去使用单独的同步Write(2)写入磁盘中。然而如果Redis服务阻塞或者被系统管理员杀死,那么将可能导致只有部分的操作被执行。Redis在重启的时候将会检测当前状态,并退出这个错误。它可以使用Redis-Check-AOF工具移除部分的事务,去修复这个AOF文件,所以Redis可以再次启动。

从Redis2.2版本开始,redis有了一个额外的保证,check-and-set(CAS)操作形式非常像乐观锁。
CAS的文档就在这一页

用法

Redis事务从你输入MULTI命令开始,这个命令总是答复OK。从这个时候开始用户可以提出多种命令,Redis将会把他们入队,而不是执行这些命令。所有的命令只有在EXEC调用之后才会执行。

调用DISCARD命令将会刷新事务中的队列并退出这个事务。

这里有个一个原子增长foobar键例子

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

在这个回话之后可能看到EXEC返回一个答复数组,数组里面的每一个元素的顺序和事务中命令发送的相同。

当Redis连接到一个MULTI请求上下文环境中,所有的命令发送后都会得到一个字符串答复QUEUED(这是redis事务协议的发送一个状态答复)。当EXEC命令被调用后,一组队列命令将会按照简单的时间顺序来执行。

事务中的错误

在事务中可能会碰到的两种命令错误

  • 一个可能是命令入队失败,这个在命令在执行调用之前会发生错误。例如,这个命令可能有语法错误(错误的参数数量,错误的命令名),或者其他某些紧急的状态,如内存溢出(如果redis配置文件配置了内存最大限制指令)
  • 一个可能是执行调用之后失败,例如,从我们施行了一个由于错误的value的key操作(例如对着String类型的value施行了List命令操作)

客户端发生了第一个错误情况,在exec执行之前发生的。通过检查队列命令返回值:如果这个命令回答这个队列的命令是正确的,否者redis会返回一个错误。如果那里发生了一个队列命令错误,大部分客户端将会退出并丢弃这个事务。

然后从reids2.6.5开始,redis服务将会记住在这块命令中有一个错误,并且将会拒绝执行这个事务并在执行时返回一个错误,和自动退出这个事务。

在redis2.6.5之前,客服端只会执行那些成功的队列命令子集,以免客户端执行先前被忽略的错误。这个新的行为使得最小的流水线事务变得更加容易,所以这个事务会被同时发送,读取所有的应答。

至于那些错误发生在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并不会停止执行其他的命令。

另一个例子,当事务中的命令在入队时,发生语法错误,会立刻报告。

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

这次是由于错误的INCR命令具有语法错误无法入队。

问什么redis不支持事务回滚

如果你相关的数据库经验,因为事务中的命令可能会失败,但redis事务还会执行其他的,而不是事务回滚,这对你来说可能有些奇怪

但是对于这种行为有好的观点

  • Redis的命令是会失败的,如果使用了错误的语法(这个命令入队时无法被检测错误),或者使用了错误的数据类型的键:这就意味着实际上错误的命令会返回一个程序错误的答复。
  • Redis的内部极其简单和快速,来源于它不需要回滚功能。

一个说会产生Bug论据的反对Redis观点,但是需要注意的是,通常回滚并不能解决来自编程的错误。举个例子,你本来想+1,却+2了,又或者+在错误的类型上。回滚并不能解决。由于无法提供一个避免程序员自己的错误,而这种错误在产品中并不会出现,所以我们选择了一个简单和快速的方法去支持回滚这个事务。

放弃事务

DISCARD 被使用会忽略这个事务,在这种情况下,不执行命令,连接状态恢复正常。

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

使用 check-and-set 操作实现乐观锁

WATCH命令为Redis事务提供了CAS行为

WATCH监控的值,会被检测是否被改变。如果有一个以上的值在EXEC调用前被改变,那么整个事务将会被取消。EXEC会会犯多个空答复(@nil-reply)来表示这个事务早就失败了

举个例子,假设我们需要原子操作为一个值+1,这个(假设这个增加的值不存在)。

第一步像这样

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

上面这个实现可能在一个客户端中表现的很好。但是如果多个客户端同时对这个键操作,那么就会产生竞争。
举个例子,客户端A和B都读到了老的值10,去加+1,
两个客户端都SET key 11 .那么最后这个值可能变成11,而不是12

WATCH就可以很好的解决这个问题

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

使用上面的代码,如果这里和其他的客户端产生了竞争去修改这个这个VAL的值。
在我们调用WATCHEXEC后,这个事务就失败

我们仅仅需要去重复这整个操作就可以了,这就是强大的乐观锁。
在许多例子中,多个客户端将会访问不同的KEY,所以很少发生冲突,通常我们不需要重复这个操作

WATCH 说明

那么WATCH究竟是怎么样的? EXEC 命令执行需要有条件: 事务只能在所有被监视键都没有被修改的前提下(但是他们可能同时更改它在相同的客户端事务下而没有中断事务详情.)执行, 如果这个前提不能满足的话,事务就不会被执行。

可以多次调用WATCH。 所有的“WATCH”调用后都开始检测改变,直到“EXEC”被调用的时刻。一个WATCH可以同时检测多个KEY

EXEC被执行后,所有的key都会被取消检测UNWATCH,不论这个事务是否结束。当然如果这个客户端断开了,一切都会UNWATCH

也可以使用无参数UNWATCH命令来取消所有被监视的键。有时,这是有用的,因为我们使用了乐观锁锁定几个键,因为可能我们需要执行一个事务来更改这些键,但是在读取后我们不想继续的监控键的当前内容之后。 当这种情况发生时,我们只需调用“UNWATCH”,以便其他连接可以自由使用新的事务了。

使用 WATCH 去实现ZPOP(ZSET)

举个例子说明怎样使用WATCH,可以创建一个新的原子操作,另外去实现redis不支持的ZPOP。
这是一个从a中弹出分数较低的原子命令实现方式。这也是最简单的方法。

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

如果EXEC执行失败(返回了 @nil-reply),我们需要重复这个操作

Redis 脚本和事务

由于复制的原因redis2.6一直才支持脚本,然而事务却已经存在很久了。不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本,也可以避免竞争条件的方法, 而且redis事务本身的实现并不复杂。

不过在不远的将来, 可能所有用户都会只使用脚本来实现事务也说不定。 如果真的发生这种情况的话, 那么我们将废弃并最终移除事务功能。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/99773.html

(0)
上一篇 2021年8月21日 16:53
下一篇 2021年8月21日 17:09

相关推荐

发表回复

登录后才能评论