概述
Redis通过MULTI,EXEC,WATCH等命令来实现事务功能
以下是一个事务的执行过程,该事务由一个MULTI命令开始,接着将多个命令放入事务当众,最后由EXEC命令将这个事务提交给服务器
1 | redis> MULTI |
事务的实现
一个事务从开始到结束通常会经历以下三个阶段:
- 事务开始
- 命令入队
- 事务执行
事务开始
MULTI命令的执行标志着事务的开始,将执行该命令的客户端从非事务状态切换到事务状态
命令入队
当一个客户端处于非事务状态时,这个客户端发送的命令会立即被服务端执行
当一个客户端处于事务状态时,服务端会根据这个客户端发来的不同命令执行不同的操作:
- 如果客户端发送的命令是EXEC,DISCARD,WATCH,MULTI四个命令中的一个,那么服务器会立即执行这个命令
- 如果是那四个之外的命令,服务器不会立即执行这个命令,而是将这个命令放入一个事务队列里面,然后向客户端返回QUEUED回复
事务队列
每个Redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的mstate属性里面
事务状态包含一个事务队列,以及一个已入队命令的计数器
事务队列是一个multiCmd类型的数组,数组中每个multi结构都保存了一个已入队命令的相关信息,包括指向命令实现函数的指针,命令的参数,以及参数的数量
事务队列以FIFO的方式保存入队的命令.
执行事务
当一个处于事务状态的客户端向服务器发送EXEC命令时,这个EXEC命令将立即被服务器执行.服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果全部返回给客户端
WATCH命令的实现
WATCH命令是一个乐观锁,它可以在EXEC命令执行之前监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复
一个事务失败的例子:
1 | redis> WATCH "name" |
发生上面的情况是因为:
时间 | 客户端A | 客户端B |
---|---|---|
T1 | WATCH “name” | |
T2 | MULTI | |
T3 | SET “name” “peter” | |
T4 | SET “name” “john” | |
T5 | EXEC |
使用WATCH命令监视数据库键
每个Redis数据库都保存着一个watched_keys字典,这个字典的键是某个被WATCH命令监视的数据库键,而字典的值则是一个链表,链表中记录了所有监视响应数据库键的客户端
监视机制的触发
所有对数据库进行修改的命令在执行之后斗湖调用touchWatchKey函数对watched_keys字典进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么touchWatchKey函数将监视被修改键的客户端的REDIS_DIRTY_CAS标识打开,表示该客户端的事务安全性已经被破坏
判断事务是否安全
当服务器收到一个客户端发来的EXEC命令时,服务器会根据这个客户端是否打开了REDIS_DIRTY_CAS标识来决定是否执行事务
一个完整的WATCH事务执行过程
假设当前客户端为c10086,而数据库watched_keys字典的当前状态如图19-7所示,那么当c10086执行以下WATCH命令之后:
1 | c10086> WATCH "name" |
watched_keys字典将更新至图19-8所示的状态:
接下来,客户端c10086继续向服务器发送MULTI命令,并将一个SET命令放入事务队列:
1 | c10086> MULTI |
这时,另一个客户端c999向服务器发送了一条SET命令,设置"name"键值
1 | c999> SET "name" "john" |
c999执行的这个SET命令会导致正在监视"name"键的所有客户端的REDIS_DIRTY_CAS标识被打开,其中包括客户端c10086.
之后,c10086向服务器发送EXEC命令的时候,因为c10086的REDIS_DIRTY_CAS已经被打开,所以服务器将拒绝执行它提交的事务
1 | c10086> EXEC |
事务的ACID性质
在Redis中,事务总有原子性,一致性和隔离性,并且当Redis运行在某种特定的持久化模式下时,事务也具有持久性
重点回顾
- 在事务中多个命令会被入队到FIFO队列中等待执行
- 事务在执行过程中不会被终端,当十五队列中的梭鱼命令都被执行完毕之后,事务才会结束
- 带有WATCH命令的事务会将客户端和被监视的键在数据库的watched_keys字典中进行关联,当键值被修改时,程序会将所有监视被修改的键的客户端的REDIS_DIRTY_CAS标识打开
- 只有在REDIS_DIRTY_CAS标志未被打开时,服务器才会执行客户端提交的事务,否则的话,服务器将拒绝执行客户端提交的事务
- Redis的事务总是具有ACID中的原子性,一致性和隔离性的,当服务器运行在AOF持久化模式下,并且appendfsync选项值为always时,事务也具有耐久性