面试问题(谈谈redis事务)

⾯试问题(谈谈redis事务
MULTI、EXEC、DISCARD和WATCH命令是Redis事务功能的基础。Redis事务允许在⼀次单独的步骤中执⾏⼀组命令,并且可以保证如下两个重要事项:
>Redis会将⼀个事务中的所有命令序列化,然后按顺序执⾏。Redis不可能在⼀个Redis事务的执⾏过程中插⼊执⾏另⼀个客户端发出的请求。这样便能保证Redis将这些命令作为⼀个单独的隔离操作执⾏。 > 在⼀个Redis事务中,Redis要么执⾏其中的所有命令,要么什么都不执⾏。因此,Redis事务能够保证原⼦性。EXEC命令会触发执⾏事务中的所有命令。因此,当某个客户端正在执⾏⼀次事务时,如果它在调⽤MULTI命令之前就从Redis服务端断开连接,那么就不会执⾏事务中的任何操作;相反,如果它在调⽤EXEC命令之后才从Redis服务端断开连接,那么就会执⾏事务中的所有操作。当Redis使⽤只增⽂件(AOF:Append-only File)时,Redis能够确保使⽤⼀个单独的write(2)系统调⽤,这样便能将事务写⼊磁盘。然⽽,如果Redis服务器宕机,或者系统管理员以某种⽅式停⽌Redis服务进程的运⾏,那么Redis很有可能只执⾏了事务中的⼀部分操作。Redis将会在重新启动时检查上述状态,然后退出运⾏,并且输出报错信息。使⽤redis-check-aof⼯具可以修复上述的只增⽂件,这个⼯具将会从上述⽂件中删除执⾏不完全的事务,这样Redis服务器才能再次启动。
从2.2版本开始,除了上述两项保证之外,Redis还能够以乐观锁的形式提供更多的保证,这种形式⾮常类似于“检查再设置”(CAS:Check And Set)操作。本⽂稍后会对Redis的乐观锁进⾏描述。
⼀、相关命令
1. MULTI
⽤于标记事务块的开始。Redis会将后续的命令逐个放⼊队列中,然后才能使⽤EXEC命令原⼦化地执⾏这个命令序列。
这个命令的运⾏格式如下所⽰:
MULTI
这个命令的返回值是⼀个简单的字符串,总是OK。
2. EXEC
在⼀个事务中执⾏所有先前放⼊队列的命令,然后恢复正常的连接状态。
当使⽤WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执⾏事务中的命令,这种⽅式利⽤了检查再设置(CAS)的机制。
这个命令的运⾏格式如下所⽰:
EXEC
这个命令的返回值是⼀个数组,其中的每个元素分别是原⼦化事务中的每个命令的返回值。当使⽤WATCH命令时,如果事务执⾏中⽌,那么EXEC命令就会返回⼀个Null值。
3. DISCARD
清除所有先前在⼀个事务中放⼊队列的命令,然后恢复正常的连接状态。
如果使⽤了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控。
这个命令的运⾏格式如下所⽰:
DISCARD
这个命令的返回值是⼀个简单的字符串,总是OK。
4. WATCH
当某个事务需要按条件执⾏时,就要使⽤这个命令将给定的键设置为受监控的。
这个命令的运⾏格式如下所⽰:
WATCH key [key ...]
这个命令的返回值是⼀个简单的字符串,总是OK。
对于每个键来说,时间复杂度总是O(1)。
5. UNWATCH
清除所有先前为⼀个事务监控的键。
如果你调⽤了EXEC或DISCARD命令,那么就不需要⼿动调⽤UNWATCH命令。
这个命令的运⾏格式如下所⽰:
UNWATCH
这个命令的返回值是⼀个简单的字符串,总是OK。
时间复杂度总是O(1)。
⼆、使⽤⽅法
使⽤MULTI命令便可以进⼊⼀个Redis事务。这个命令的返回值总是OK。此时,⽤户可以发出多个Redis命令。Redis会将这些命令放⼊队列,⽽不是执⾏这些命令。⼀旦调⽤EXEC命令,那么Redis就会执⾏事务中的所有命令。
阿维菌素油膏
相反,调⽤DISCARD命令将会清除事务队列,然后退出事务。
以下⽰例会原⼦化地递增foo键和bar键的值:
正如从上⾯的会话所看到的⼀样,EXEC命令的返回值是⼀个数组,其中的每个元素都分别是事务中的每个命令的返回值,返回值的顺序和命令的发出顺序是相同的。
当⼀个Redis连接正处于MULTI请求的上下⽂中时,通过这个连接发出的所有命令的返回值都是QUEUE字符串(从Redis协议的⾓度来看,返回值是作为状态回复(Status Reply)来发送的)。当调⽤EXEC命令时,Redis会简单地调度执⾏事务队列中的命令。
三、事务内部的错误
在⼀个事务的运⾏期间,可能会遇到两种类型的命令错误:
⼀个命令可能会在被放⼊队列时失败。因此,事务有可能在调⽤EXEC命令之前就发⽣错误。例如,这个命令可能会有语法错误(参数
的数量错误、命令名称错误,等等),或者可能会有某些临界条件(例如:如果使⽤maxmemory指令,为Redis服务器配置内存限制,
那么就可能会有内存溢出条件)。
在调⽤EXEC命令之后,事务中的某个命令可能会执⾏失败。例如,我们对某个键执⾏了错误类型的操作(例如,对⼀个字符串(String)类型的键执⾏列表(List)类型的操作)。
可以使⽤Redis客户端检测第⼀种类型的错误,在调⽤EXEC命令之前,这些客户端可以检查被放⼊队列的命令的返回值:如果命令的返回值是QUEUE字符串,那么就表⽰已经正确地将这个命令放⼊队列;否则,Redis将返回⼀个错误。如果将某个命令放⼊队列时发⽣错误,那么⼤多数客户端将会中⽌事务,并且丢弃这个事务。
然⽽,从Redis 2.6.5版本开始,服务器会记住事务积累命令期间发⽣的错误。然后,Redis会拒绝执⾏这个事务,在运⾏EXEC命令之后,便会返回⼀个错误消息。最后,Redis会⾃动丢弃这个事务。
在Redis 2.6.5版本之前,如果发⽣了上述的错误,那么在客户端调⽤了EXEC命令之后,Redis还是会运⾏这个出错的事务,执⾏已经成功放⼊事务队列的命令,⽽不会关⼼先前发⽣的错误。从2.6.5版本开始,Redis在遭遇上述错误时,会采⽤先前描述的新⾏为,这样便能轻松地混合使⽤事务和管道。在这种情况下,客户端可以⼀次性地将整个事务发送⾄Redis服务器,稍后再⼀次性地读取所有的返回值。
大小头相反,在调⽤EXEC命令之后发⽣的事务错误,Redis不会进⾏任何特殊处理:在事务运⾏期间,即使某个命令运⾏失败,所有其他的命令也将会继续执⾏。
这种⾏为在协议层⾯上更加清晰。在以下⽰例中,当事务正在运⾏时,有⼀条命令将会执⾏失败,即使这条命令的语法是正确的:
上述⽰例的EXEC命令的返回值是批量的字符串,包含两个元素,⼀个是OK代码,另⼀个是-ERR错误消息。客户端会根据⾃⾝的程序库,选择⼀种合适的⽅式,将错误信息提供给⽤户
需要注意的是,即使某个命令执⾏失败,事务队列中的所有其他命令仍然会执⾏ —— Redis不会停⽌执⾏事务中的命令。
再看另⼀个⽰例,再次使⽤telnet通信协议,观察命令的语法错误是如何尽快报告给⽤户的:
这⼀次,由于INCR命令的语法错误,Redis根本就没有将这个命令放⼊事务队列。
安亭事件四、为什么Redis不⽀持回滚?
如果你具备关系型数据库的知识背景,你就会发现⼀个事实:在事务运⾏期间,虽然Redis命令可能会执⾏失败,但是Redis仍然会执⾏事务中余下的其他命令,⽽不会执⾏回滚操作,你可能会觉得这种⾏为很奇怪。
然⽽,这种⾏为也有其合理之处:
只有当被调⽤的Redis命令有语法错误时,这条命令才会执⾏失败(在将这个命令放⼊事务队列期间,Redis能够发现此类问题),或者
对某个键执⾏不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执⾏失败,这种错误很有可能在程序开发期间发现,⼀般很少在⽣产环境发现。
Redis已经在系统内部进⾏功能简化,这样可以确保更快的运⾏速度,因为Redis不需要事务回滚的能⼒。
对于Redis事务的这种⾏为,有⼀个普遍的反对观点,那就是程序有可能会有缺陷(bug)。但是,你应当注意到:事务回滚并不能解决任何程序错误。例如,如果某个查询会将⼀个键的值递增2,⽽不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。请注意,没有⼈能解决程序员⾃⼰的错误,这种错误可能会导致Redis命令执⾏失败。正因为这些程序错误不⼤可能会进⼊⽣产环境,所以我们在开发Redis时选⽤更加简单和快速的⽅法,没有实现错误回滚的功能。
五、丢弃命令队列
DISCARD命令可以⽤来中⽌事务运⾏。在这种情况下,不会执⾏事务中的任何命令,并且会将Redis连接恢复为正常状态。⽰例如下所⽰:
六、通过CAS操作实现乐观锁
Redis使⽤WATCH命令实现事务的“检查再设置”(CAS)⾏为。
作为WATCH命令的参数的键会受到Redis的监控,Redis能够检测到它们的变化。在执⾏EXEC命令之前,如果Redis检测到⾄少有⼀个键被修改了,那么整个事务便会中⽌运⾏,然后EXEC命令会返回⼀个Null值,提醒⽤户事务运⾏失败。
例如,设想我们需要将某个键的值⾃动递增1(假设Redis没有INCR命令)。
⾸次尝试的伪码可能如下所⽰:
val = GET mykey
手机回收系统val = val + 1
SET mykey $val
如果我们只有⼀个Redis客户端在⼀段指定的时间之内执⾏上述伪码的操作,那么这段伪码将能够可靠的⼯作。如果有多个客户端⼤约在同⼀时间尝试递增这个键的值,那么将会产⽣竞争状态。例如,客户端-A和客户端-B都会读取这个键的旧值(例如:10)。这两个客户端都会将这个键的值递增⾄11,最后使⽤SET命令将这个键的新值设置为11。因此,这个键的最终值是11,⽽不是12。
现在,我们可以使⽤WATCH命令完美地解决上述的问题,伪码如下所⽰:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
由上述伪码可知,如果存在竞争状态,并且有另⼀个客户端在我们调⽤WATCH命令和EXEC命令之间的时间内修改了val变量的结果,那么事务将会运⾏失败。
我们只需要重复执⾏上述伪码的操作,希望此次运⾏不会再出现竞争状态。这种形式的锁就被称为乐观锁,它是⼀种⾮常强⼤的锁。在许多⽤例中,多个客户端可能会访问不同的键,因此不太可能发⽣冲突 —— 也就是说,通常没有必要重复执⾏上述伪码的操作。
七、WATCH命令详解
那么WATCH命令实际做了些什么呢?这个命令会使得EXEC命令在满⾜某些条件时才会运⾏事务:我们要求Redis只有在所有受监控的键都没有被修改时,才会执⾏事务。(但是,相同的客户端可能会在事务内部修改这些键,此时这个事务不会中⽌运⾏。)否则,Redis根本就不会进⼊事务。(注意,如果你使⽤WATCH命令监控⼀个易失性的键,然后在你监控这个键之后,Redis再使这个键过期,那么EXEC命令仍然可以正常⼯作。)
大渡河造林局WATCH命令可以被调⽤多次。简单说来,所有的WATCH命令都会在被调⽤之时⽴刻对相应的键进⾏监控,直到EXEC命令被调⽤之时为⽌。你可以在单条的WATCH命令之中,使⽤任意数量的键作为命令参数。
当调⽤EXEC命令时,所有的键都会变为未受监控的状态,Redis不会管事务是否被中⽌。当⼀个客户单连接被关闭时,所有的键也都会变为未受监控的状态。私营企业主不能入党
你还可以使⽤UNWATCH命令(不需要任何参数),这样便能清除所有的受监控键。当我们对某些键施加乐观锁之后,这个命令有时会⾮常有⽤。因为,我们可能需要运⾏⼀个⽤来修改这些键的事务,但是在读取这些键的当前内容之后,我们可能不打算继续进⾏操作,此时便可以使⽤UNWATCH命令,清除所有受监控的键。在运⾏UNWATCH命令之后,Redis连接便可以再次⾃由地⽤于运⾏新事务。
如何使⽤WATCH命令实现ZPOP操作呢?
本⽂将通过⼀个⽰例,说明如何使⽤WATCH命令创建⼀个新的原⼦化操作(Redis并不原⽣⽀持这个原⼦化操作),此处会以实现ZPOP操作为例。这个命令会以⼀种原⼦化的⽅式,从⼀个有序集合中弹出分数最低的元素。以下源码是最简单的实现⽅式:
WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
如果伪码中的EXEC命令执⾏失败(例如,返回Null值),那么我们只需要重复运⾏这个操作即可。
⼋、Redis脚本和事务
根据定义,Redis脚本也是事务型的。因此,你可以通过Redis事务实现的功能,同样也可以通过Redis脚本来实现,⽽且通常脚本更简单、更快速。
由于Redis从2.6版本才开始引⼊脚本特性,⽽事务特性是很久以前就已经存在的,所以⽬前的版本才有两个看起来重复的特性。但是,我们不太可能在短时间内移除对事务特性的⽀持。因为,即使不⽤求助于Redis脚本,⽤户仍然能够规避竞争状态,这从语义上来看是适宜的。还有另⼀个更重要的原因,Redis事务特性的实现复杂度是最⼩的。
但是,在相当长的⼀段时间之内,我们不⼤可能看到整个⽤户体都只使⽤Redis脚本。如果发⽣这种情况,那么我们可能会废弃,甚⾄最终移除Redis事务。

本文发布于:2024-09-23 04:37:38,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/212380.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:命令   事务   错误   可能
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议