Redis-事务

概述

Redis通过MULTI,EXEC,WATCH等命令来实现事务功能

以下是一个事务的执行过程,该事务由一个MULTI命令开始,接着将多个命令放入事务当众,最后由EXEC命令将这个事务提交给服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
redis> MULTI
OK

redis> SET "name" "Practical Common Lisp"
QUEUED

redis> GET "name"
QUEUED

redis> SET "author" "Peter Seibel"
QUEUED

redis> GET "author"
QUEUED

redis> EXEC
1) OK
2) "Practical Common Lisp"
3) OK
4) "Peter Seibel"

事务的实现

一个事务从开始到结束通常会经历以下三个阶段:

  1. 事务开始
  2. 命令入队
  3. 事务执行

事务开始

MULTI命令的执行标志着事务的开始,将执行该命令的客户端从非事务状态切换到事务状态

命令入队

当一个客户端处于非事务状态时,这个客户端发送的命令会立即被服务端执行
当一个客户端处于事务状态时,服务端会根据这个客户端发来的不同命令执行不同的操作:

  • 如果客户端发送的命令是EXEC,DISCARD,WATCH,MULTI四个命令中的一个,那么服务器会立即执行这个命令
  • 如果是那四个之外的命令,服务器不会立即执行这个命令,而是将这个命令放入一个事务队列里面,然后向客户端返回QUEUED回复

事务队列

每个Redis客户端都有自己的事务状态,这个事务状态保存在客户端状态的mstate属性里面

事务状态包含一个事务队列,以及一个已入队命令的计数器
事务队列是一个multiCmd类型的数组,数组中每个multi结构都保存了一个已入队命令的相关信息,包括指向命令实现函数的指针,命令的参数,以及参数的数量

事务队列以FIFO的方式保存入队的命令.

执行事务

当一个处于事务状态的客户端向服务器发送EXEC命令时,这个EXEC命令将立即被服务器执行.服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果全部返回给客户端

WATCH命令的实现

WATCH命令是一个乐观锁,它可以在EXEC命令执行之前监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复

一个事务失败的例子:

1
2
3
4
5
6
7
8
9
10
11
redis> WATCH "name"
OK

redis> MULTI
OK

redis> SET "name" "peter"
QUEUED

redis> EXEC
(nil)

发生上面的情况是因为:

时间 客户端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
2
c10086> WATCH "name"
OK

watched_keys字典将更新至图19-8所示的状态:
20190918204413.png

接下来,客户端c10086继续向服务器发送MULTI命令,并将一个SET命令放入事务队列:

1
2
3
4
5
c10086> MULTI
OK

SET "name" "peter"
QUEUED

这时,另一个客户端c999向服务器发送了一条SET命令,设置"name"键值

1
2
c999> SET "name" "john"
OK

c999执行的这个SET命令会导致正在监视"name"键的所有客户端的REDIS_DIRTY_CAS标识被打开,其中包括客户端c10086.

之后,c10086向服务器发送EXEC命令的时候,因为c10086的REDIS_DIRTY_CAS已经被打开,所以服务器将拒绝执行它提交的事务

1
2
c10086> EXEC
(nil)

事务的ACID性质

在Redis中,事务总有原子性,一致性和隔离性,并且当Redis运行在某种特定的持久化模式下时,事务也具有持久性

重点回顾

  • 在事务中多个命令会被入队到FIFO队列中等待执行
  • 事务在执行过程中不会被终端,当十五队列中的梭鱼命令都被执行完毕之后,事务才会结束
  • 带有WATCH命令的事务会将客户端和被监视的键在数据库的watched_keys字典中进行关联,当键值被修改时,程序会将所有监视被修改的键的客户端的REDIS_DIRTY_CAS标识打开
  • 只有在REDIS_DIRTY_CAS标志未被打开时,服务器才会执行客户端提交的事务,否则的话,服务器将拒绝执行客户端提交的事务
  • Redis的事务总是具有ACID中的原子性,一致性和隔离性的,当服务器运行在AOF持久化模式下,并且appendfsync选项值为always时,事务也具有耐久性