概述
上一节(Redis分布式锁)介绍了Lua脚本具有原子性,可以用于实现事务性。这节介绍Redis API提供的事务性,以命令类型分成两部分介绍,最后再介绍Redis为什么不支持回滚操作。
MULTI, EXEC, DISCARD命令
事务性提供一种将多个命令一起一次性顺序提交执行的机制,并且在执行期间不会被中断,直到所有命令被执行完成。
事务以MULTI命令为开始,接着将多个命令放入事务队列中,最后由EXEC命令提交服务器执行,如果DISCARD命令代替EXEC命令则清空事务队列中所有的命令,并且退出事务。
事务队列按照先进先出(FIFO)的方法保存入队的命令。
如果在事务中存在错误,Redis如何应对?我们按照用户输入命令,提交命令的操作顺序来分析可能发生的情况:首先,用户在客户端输入命令时,如果输入的是一个错误的命令,Redis客户端对这种错误是敏感的,会马上提示输入的命令错误。然后在最后执行EXEC命令提交时,运行的结果以版本Redis 2.6.5为分界,从Redis 2.6.5版本开始,Redis服务端会记录之前的这个错误,然后因为这个错误导致这个事务被丢弃;在Redis 2.6.5版本之前,Redis服务端会忽略这个错误,执行除了这个错误命令以外的所有正确入队的命令。最后,如果执行EXEC命令之前没有发生错误,那么入队的所有命令都会被执行,即使其中有一些命令在执行过程中出错。
也就是说,根据Redis处理错误的方式,可能导致事务队列中一部分命令被执行,一部分命令没有被执行成功。这个在实际中要特别注意,因为可能引起程序结果的错误,举个例子,A需支付给B10元钱,A使用字符串对象存储现有的资金50元,B使用列表对象存储现有的资金60元,在事务中需要A资金先减去10元,B的资金再加上10元,交易结束,事务完成。事务的命令如下:
>multi
OK
>DECRBY A 10
QUEUED
>INCRBY B 10
QUEUED
>exec
- 30
- WRONGTYPE Operation against a key holding the wrong kind of value
错误地对B的列表对象执行了加法操作,此时结果只会看见A少了10元钱为40元,B的钱数未变仍旧是60元。这种情况在关系型数据库中涉及到回滚操作,而Redis并不支持回滚操作。
WATCH, UNWATCH命令
WATCH命令是一个乐观锁(optimistic lock),可以在EXEC命令之前监视Redis中的任意数量的键,并且在执行EXEC命令时,执行先检查再设置的操作(check-and-set),去检查监视的键值是否被修改,如果被修改,服务器将拒绝执行事务,只有未被修改时,才执行事务。UNWATCH命令解除对Redis中键的监视。
乐观锁(optimistic lock)假定并发的现象并不会经常发生,不对数据进行任何加锁控制,而在提交执行时一旦发现数据被其他线程修改,再执行相应的处理失败的策略。换言之,认为系统中偶尔执行处理失败策略的成本会低于读取数据时对数据加锁的成本。Redis的WATCH命令认为在执行事务时,一般不会产生数据的冲突,如果在提交执行时发现监视的数据被修改则拒绝事务的执行。
Redis为什么不支持回滚操作
Redis官方(Redis官网事务性介绍)给出的解释是,支持回滚操作与Redis追求的简洁和高效的宗旨不符合;并且事务执行的错误都是由错误的语法和键所具有错误的数据对象所导致的,这些错误都是程序编写的错误,在开发中容易被检查到并且修改的,不会出现在生产环境中。就如我们之前的举例,A支付给B10元钱,错误出现在对B使用的列表对象执行加法操作,在开发过程中很容易发现和定位这个错误,再者记录现金的数据结构,尤其是在A用字符串对象的前提下,B使用列表对象本身就不符合常理,这显然是编程的错误。
总结
Redis事务性的使用就是对Redis事务性命令的使用,掌握这些命令的用法可以高效开发基于Redis事务性程序。在掌握了这些命令之后,着重记住几个知识点:
- 如果在事务中存在错误,Redis如何应对?
- 乐观锁机制
- Redis为什么不支持回滚操作?