redis之事务
redis 通过 multi、watch、exec等命令来实现事务功能。
redis提供这样一种将多个待执行的命令入队,在事务开始之后执行入队的事务命令,即使事务队列中部分命令执行失败也不会中断事务,事务队列中的所有命令执行完毕后,才会去请求其他客户端的命令请求。
事务的实现
redis中事务从开始到结束有如下三个阶段:
- 事务开始
- 命令入队
- 事务执行
命令开始
redis中,客户端使用multi
命令标识当前客户端进入事务状态。
该命令将执行该命令的客户端从非事务状态切换到事务状态#define CLIENT_MULTI (1<<3)
。
1 | typedef struct client { |
命令入队
客户端处于非事务状态时,这个客户端发送的命令将会被服务端立即执行。
而客户端处于事务状态时,服务器会根据这个客户端发来的不同命令执行不同的操作:
- 如果是
multi、exec、discard、watch
四个命令中的一个,服务器立即执行。 - 否则,将命令放入事件队列中,向客户端返回
QUEUED
回复。
事务队列
客户端中使用multiState mstate
保存事务队列,其定义如下
1 | typedef struct multiState { |
multiState
中记录了事务命令,事务命令个数。其中,事务队列是一个FIFO
队列。commands
则保存了每个入队的命令:
1 | typedef struct multiCmd { |
客户端执行如下命令:
1 | MULTI |
事务执行
处于事务状态的客户端向服务器发送exec
命令时,该命令将被立即执行。服务端遍历客户端的事务队列,执行队列中保存的所有命令,最后将执行命令所得的结果返回给客户端。
watch 命令的实现
watch
命令是乐观锁,在执行exec
命令之前,监视任意数量的数据库键,在执行EXEC
命令时,检查被监视的键是否至少有一个已经被修改过,如果是,服务器将拒绝执行事务,并向事务所在的客户端发送代表事务执行失败的空返回。
watch 监视 键
数据库中使用watch_keys
字典记录数据库中的键,其中字典key
为被watch
命令监视的数据库键,字典值为所有监控相关数据库键的客户端。
1 | typedef struct redisDb { |
监视触发
所有对数据库进行修改的命令,比如set、lpush、sadd、zrem,del
在执行之后都会调用multi.c/touchKey
函数对watched_keys
进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有,将当前客户端的flags
标识为#define CLIENT_DIRTY_CAS (1<<5)
判断事务是否安全
当服务端收到户端发送exec
命令时,服务器将根据这个客户端的flags
是否打开了CLIENT_DIRTY_CAS
标识来决定是否执行事务。
- 如果
CLIENT_DIRTY_CAS
被打开,则说明客户端监视的数据库键中至少有一个已经被修改了,客户端提交的事务不再安全,服务器拒绝执行客户端提交的事务。 - 否则,认为事务安全,继续执行事务队列里的命令。
疑问质疑?
集群模式下是否不支持事务?
1 | 182.168.168.238:6379> multi |
(集群模式下使用事务遇到的问题)
的确如此!