Redis还提供了事务机制,MULTI, EXEC, DISCARD以及WATCH等命令构成了Redis事务机制的基础。本文将会介绍Redis事务的一些基本特性及使用方法等,并对乐观锁做了一些基础的介绍。
1.Redis事务特性
Redis事务机制可以使用户一次执行多个命令,并且提供了两点重要保证:
(1)顺序性
一个事务中的所有命令都是严格串行化、顺序化的执行的。也就是说,在Redis服务执行事务的过程中,Redis服务绝不会去服务由另一个客户端发出的请求。这就保证了,事务中的命令集被当做一个单独的操作被执行。
(2)原子性
Redis事务同时还具有原子性:事务中的命令要么全部执行,要么一个也不执行。首先, Redis事务使用MULTI命令来标记一个事务的开始,后续的命令将被放入队列中,EXEC命令来执行事务原子操作。所以,如果在执行EXEC命令之前,客户端与Redis服务断开了连接,那么事务中的所有命令都不会被执行;相反地,一旦Redis服务顺利的接收到了EXEC命令,那么事务中的所有命令都会被执行。
但是也会存在一些意外情况,如果Redis服务崩溃或者被系统管理员强制杀死,那么Redis服务也有可能只注册了一部分的操作。
2.使用方法
(1)MULTI
MULTI命令标识一个Redis事务的开始,该命令总是返回OK。输入MULTI命令之后,用户可以输入多个待执行的命令,Redis并不会去立即执行这些命令,而是将其添加到一个队列中去。
(2)EXEC
该命令表示执行先前加入到队列中的所有命令。比如,有如下示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set foo 1 QUEUED 127.0.0.1:6379> INCR foo QUEUED 127.0.0.1:6379> INCR foo QUEUED 127.0.0.1:6379> exec 1) OK 2) (integer) 2 3) (integer) 3 |
(3)DISCARD
该命令将会清空事务队列里的命令,并退出事务:
1 2 3 4 5 6 7 8 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> INCR foo QUEUED 127.0.0.1:6379> INCR foo QUEUED 127.0.0.1:6379> DISCARD OK |
3.错误检查
Redis事务中常见的错误有两种:一种错误在执行EXEC前就会被发现;另一种错误需要在EXEC运行之后才可能被发现。
(1)EXEC前
在执行EXEC前,命令会被放入到一个命令队列中去。如果这个命令有严重的语法错误,那么将无法将其加入命令队列中,比如:
1 2 3 4 5 6 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET counter 1 QUEUED 127.0.0.1:6379> INCRR counter #错误命令名:INCR多了一个R (error) ERR unknown command 'INCRR' |
这种错误命令名、错误参数的命令以及诸如内存不足的错误,会在将命令加入队列中的时候就被发现,并提示用户。
(2)EXEC后
还有一种错误,比如,对一个string类型的对象错误的调用了list类型对象的操作方法:
1 2 3 4 5 6 7 8 9 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET counter 1 QUEUED 127.0.0.1:6379> LPOP counter QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value |
如上所示,第一个命令成功返回,第二个命令报错:string类型的对象并没有提供LPOP操作,这种错误往往在运行的时候才会进行错误检查。
但是,特别需要注意的是:EXEC之后,即使命令队列中的某个命令存在错误,Redis还是会将所有的命令全部执行下去。看如下示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET counter 1 QUEUED 127.0.0.1:6379> LPOP counter QUEUED 127.0.0.1:6379> INCR counter QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) (integer) 2 #上面的命令报错,但不影响第三个命令正常返回 127.0.0.1:6379> GET counter "2" |
由上可知,即使第二个命令报错了,但是counter的值还是从1增加到了2。所以,即使一个命令失败了,Redis还是会把所有的命令都顺序的执行完毕。这点需要牢记。
4.回滚
Redis并不支持回滚!
在事务期间,Redis命令有可能会发生错误,Redis对此错误的处理方法是不采取操作。这个做法对于熟悉关系型数据库的我们来说,有点奇怪。那么,是什么原因让Redis这样设计的呢?
首先,如前所述,Redis命令错误的原因主要有两个方面:一是语法错误;二是对某一类型的对象执行该对象不存在的操作。Redis认为:这两种情况导致的错误都是在编程过程中产生的(并且很有可能被检测出来),而不是在实际的生产过程中产生的。所以,Redis认为它并不需要回滚操作。
其次,Redis不需要回滚的能力,能够使其内部更加精简和快速。
5.乐观锁
Redis还提供了WATCH命令,该命令可以用来为Redis事务提供检查和设置(Check-and-set, CAS)的行为。比如,使用WATCH监视某个或某些key之后,如果其中的某个或某些key在执行EXEC命令之前发生了修改,将会导致整个事务中断。
比如,实际工作中我们可能面对如下的业务逻辑:
1 2 3 |
val = GET mykey #获取mykey对应的值,并赋给变量val val = val + 1 #将val的值加1 SET mykey $val #再将mykey对应的值,置为val |
单个用户操作以上示例,一般不会有问题。然而,如果在同一时间内,有多个客户端操作以上示例就比较危险了。比如,用户A和用户B都将val=10读到各自的内存中,随后A,B用户都会对val做自增操作,并将操作结果响应到mykey中。这样一来,导致mykey对应的value值为11而不是预期的12。
对此,WATCH命令(WATCH key [key …])提供了解决方案:
1 2 3 4 5 6 |
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC |
使用WATCH命令监视mykey,一旦mykey有任何修改,事务中断,保证了数据的正确性。这既是Redis提供的一种锁机制——乐观锁。
如果想要解除监视状态,则需要使用UNWATCH命令。该命令会刷新所有被监视的key,使其恢复到未被监视的状态。
6.其他
Redis脚本是事务性的,这意味着,任何可以通过Redis事务做的事情,都可以同样的通过Redis脚本实现,并且将会更加简单、快速。
参考:
https://redis.io/topics/transactions