事 务
Redis实现事务主要通过四个命令:MULTI,EXEC,DISCARD,WATCH
下面例子中以MULTI开始一个事务,将多个命令入队到事务中,最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET NAME SAM
QUEUED
127.0.0.1:6379> GET NAME
QUEUED
127.0.0.1:6379> SADD AGE 18 22 42
QUEUED
127.0.0.1:6379> SMEMBERS AGE
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) "SAM"
3) (integer) 3
4) 1) "18"
2) "22"
3) "42"
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
一、开始事务
MULTI 命令的执行标记着事务的开始:
127.0.0.1:6379> MULTI
OK
二、命令入队
当客户端处于非事务状态下时, 所有发送给服务器端的命令都会立即被服务器执行:
127.0.0.1:6379> set name kobe
OK
127.0.0.1:6379> get name
"kobe"
但是, 当客户端进入事务状态之后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回 QUEUED , 表示命令已入队:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set msg hello
QUEUED
127.0.0.1:6379> get msg
QUEUED
事务队列是一个数组:每个数组项包含三个属性
- 要执行的命令(cmd)
- 命令的参数(argv)
- 参数的个数(argc)
以本文第一个代码块为例:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET NAME SAM
QUEUED
127.0.0.1:6379> GET NAME
QUEUED
127.0.0.1:6379> SADD AGE 18 22 42
QUEUED
127.0.0.1:6379> SMEMBERS AGE
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) "SAM"
3) (integer) 3
4) 1) "18"
2) "22"
3) "42"
三、执行事务
前面说到, 当客户端进入事务状态之后, 客户端发送的命令就会被放进事务队列里。
但其实并不是所有的命令都会被放进事务队列, 其中的例外就是 EXEC 、 DISCARD 、 MULTI 和 WATCH 这四个命令 —— 当这四个命令从客户端发送到服务器时, 它们会像客户端处于非事务状态一样, 直接被服务器执行:
无论在事务状态下, 还是在非事务状态下, Redis 命令都由同一个函数执行, 所以它们共享很多服务器的一般设置, 比如 AOF 的配置、RDB 的配置,以及内存限制,等等。
不过事务中的命令和普通命令在执行上还是有一点区别的,其中最重要的两点是:
- 非事务状态下的命令以单个命令为单位执行,前一个命令和后一个命令的客户端不一定是同一个; 而事务状态则是以一个事务为单位,执行事务队列中的所有命令:除非当前事务执行完毕,否则服务器不会中断事务,也不会执行其他客户端的其他命令。
- 在非事务状态下,执行命令所得的结果会立即被返回给客户端;而事务则是将所有命令的结果集合到回复队列,再作为 EXEC 命令的结果返回给客户端。
四、带 WATCH 的事务
WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。
第一个客户端
127.0.0.1:6379> set name sam
OK
127.0.0.1:6379> watch name
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name jason
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379>
另一个客户端:在第一个客户端执行exec命令前执行以下命令
127.0.0.1:6379> set name rub
OK
127.0.0.1:6379>
在时间T4,客户端B修改了"name"键的值,当客户端A在T5执行EXEC命令时,服务器会发现WATCH监视的键“name”已经被修改,因此服务器拒绝执行客户端A的事务,并向客户端A返回空回复。
使用WATCH命令监视数据库键
每个Redis数据库保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值是一个链表,链表记录了所有监视相应数据库键的客户端。
一个watched_keys字典
上图表明:
(1) 客户端c1和c2正在监视键“name”。
(2) 客户端c3正在监视键“age”。
(3) 客户端c2和c4正在监视键“address”。
2.2 监视机制的触发
所有对数据库进行修改命令,如SET、LPUSH、SADD、ZREM、DEL等,在执行后都会对watched_keys字典进行检查,查看被修改的数据库键是否是被客户端所监视的键,如果有的话,客户端REDIS_DIRTY_CAS标识将会被打开,表示该客户端的事务安全性已经被破坏。
2.3 判断事务是否安全
当服务器接收到一个客户端发来的EXEC命令,服务器会根据这个客户端是否打开了REDIS_DIRTY_CAS标识来决定是否执行事务:
(1) 如果客户端的REDIS_DIRTY_ CAS标识已经被打开,那么说明客户端所监视的键当中,至少有一个键已经被修改过了,在这种情况下,客户端提交的事务已经不再安全,所以服务器拒绝执行客户端提交的事务。
(2) 如果客户端的REDIS_DIRTY_CAS标识没有被打开,那么说明客户端监视的所有键都没有被修改过(或者客户端没有监视任何键),事务仍然是安全的,服务器将执行客户端提交的这个事务。
五、 ACID 性质
在传统的关系式数据库中,常常用 ACID性质来检验事务功能的安全性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
Redis 事务保证了其中的一致性和隔离性,但并不保证原子性和持久性。