摘要:Redis是一个简单、高效的内存数据存储,支持多种数据结构。当然,有数据操作的地方往往都少不了事务性,Redis是否支持呢?答案是肯定的。本文将详细讲述如何redis事务使用相关的内,如有错误,欢迎指正。
Redis事务如何来实现呢?先引用Redis官方文档的一句话:
MULTI, EXEC, DISCARD and WATCH are the foundation of transactions in Redis.
我们可以理解,Redis的事务是通过MULTI,EXEC,DISCARD和WATCH这四个命令来完成的,Redis的单个命令都是原子性的,所以这里确保事务性的对象是命令集合。Reids将命令集合序列话并确保处于同一事务的命令集合连续且不被打断的执行,我们看下代码实现过程:
通过MULTI命令开启一个事务,该命令成功时返回OK。接下来可以依次输入需要来同一事务中执行的命令集合,每一次输入成功,Redis服务器都会返回QUEDED,表示该命令被成功排入队列。当需要执行该事务的时候,输入EXEC,队列中的命令会一次性执行。
如果在开启事务后,我们需要清空命令列表并关闭事务,我们需要如何操作呢?DISCARD命令为我们提供了实现.特别注意,DISCARD命令需要在开启事务后才可以执行,否则会抛出如下的错误:
在执行DISCARD命令后,事务被关闭,client被置为普通状态,我们看下具体代码实例:
在开启事务后执行set语句返回QUEUED,而在关系事务后,执行set语句返回OK,说明事务被正常关闭。但是,我们很正常的会想到,如果命令执行失败以后会怎么办呢?我们来看下一节。
Redis事务失败处理
我先引用Redis的官方解释:
If there is an error while queueing a command, most clients will abort the transaction discarding it.
It's important to note that even when a command fails, all the other commands in the queue are processed – Redis will not stop the processing of commands.
在执行EXEC之前因为语法错误(错误的命令,错误的命令参数)而导致服务器抛错,Redis2.6.5之前的版本不会将该命令放入队列。从Redis2.6.5开始,在执行EXEC时该事务将会被终止,即不执行。这里符合原子性的特点。我们以Redis4.0.8为例来看下具体的代码执行情况:
如果我们需要执行的命令语法没有错误,但是在运行时抛出错误,Redis会忽略该错误,继续执行。也许有人会有疑问,这不是违背了原子性吗?我再次给出官方的解释:
Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
Redis is internally simplified and faster because it does not need the ability to roll back.
我觉得官方的部分解释的有些牵强,他认为Redis命令的执行失败往往是可以预见的,这些错误往往在开发的时候就应该被发现而不是等到生产环境的时候。另外,Redis为了保持简单和高效天生不支持回滚,自然在执行的时候预见错误也只能继续执行下去。“命令我都执行了,是否成功我不管”,好吧,我们就姑且认为Redis在保证语法正确及运行时被操作的数据类型正确的情况下是严格原子性的吧,也正因为这点,在使用Redis事务时一定也要仔细检查命令的正确性。下面我们看下具体的代码:
事务内两条语句都被执行了(原子性),lpop语法正确,但是操作的数据类型不正确,在执行的时候报错,而set a 1命令被成功执行了。
利用watch命令实现CHECH-AND-SET(CAS)
在文章开始介绍了Redis的事务是通过MULTI,EXEC,DISCARD和WATCH这四个命令来完成的,已经介绍了三个命令,那么最后一个命令watch是用来做什么用的呢?我们先来看一个场景。
张三在某银行有一个活期账户,金额为0。某天恰巧同一时刻,张三的父亲和母亲分别向该账户汇入100元,如果我们要用Redis来完成这两次汇款操作,我们该如何操作?
简单的想象,我们或许会将每一次汇款的动作写出如下代码:
也许执行一次没有什么问题,但是如果恰巧两个线程同时执行,线程1执行第一行时money值为0,线程2执行第一行时money也为0,当两个线程分别执行后面代码时,account的值被设置为100,产生了错误。利用Redis提供的乐观锁机制可以解决这个问题。在执行money = money + 100之前我们对account进行监视,对金额进行处理后再开启事务执行后面对操作,具体代码如下:
watch命令可以监视account对值,如果一旦其值发生变化,那么接下来的事务并不会被执行并且watch也会被写在。当然在此例中,如果事务被忽略执行,我们需要再此重复执行。针对watch命令的使用,有几点注意事项:
如果被监视的key的值在事务中被改变,该客户端的事务仍然能执行,就像本例中SET account money改变了account的值,但是是在事务中,所有执行EXEC仍然能成功。
watch命令可以被执行多次,但是在失效前只有第一次生效。
可以通过UNWATCH命令卸载该客户端的的所有监视器。
当然该实例还有其他的解决方案,基于本文主题不再探讨。
后续
Redis事务性实现简单,在诸多场景中可以使用,且利用其乐观锁特性我们可以轻松的解决数据一致性问题,但是我们在利用具体语言驱动的时候需要注意连接的释放问题。后续我将分析Redis事务机制的源码。本文就到这里,欢迎一起探讨。
领取专属 10元无门槛券
私享最新 技术干货