事务
事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
事务首先以一个MULTI命令为开始,接着将多个命令放入事务当中,最后由EXEC命令将事务提交给服务器执行:
事务开始
MULTI命令通过在客户端状态的flags属性中打开REDIS_MULTI标识,将执行该命令的客户端从非事务状态切换至事务状态。
def MULTI():
# 打开事务标识
client.flags |= REDIS_MULTI
# 返回OK回复
replyOK()
命令入队
- 客户端处于非事务状态时,客户端发送的命令会立即被服务器执行。
- 客户端切换到事务状态时,服务器会根据这个客户端发来的不同命令执行不同的操作。
事务队列
每个Redis客户端将自己的事务状态保存在客户端状态的mstate属性里面:
typedef struct redisClient {
// ...
// 事务状态
multiState mstate; /* MULTI/EXEC state */
// ...
} redisClient;
事务状态为一个multiState结构,包含一个事务队列,以及一个已入队命令的计数器:
typedef struct multiState {
// 事务队列,FIFO顺序
multiCmd *commands;
// 已入队命令计数
int count;
} multiState;
事务队列是一个multiCmd类型的数组,以先进先出(FIFO)的方式保存入队的命令,每个multiCmd结构都保存了一个已入队命令的相关信息,包括指向命令实现函数的指针、命令的参数,以及参数的数量:
typedef struct multiCmd {
// 参数
robj **argv;
// 参数数量
int argc;
// 命令指针
struct redisCommand *cmd;
} multiCmd;
执行事务
当处于事务状态的客户端向服务器发送EXEC命令时,EXEC命令立即被服务器执行。服务器会遍历这个客户端的事务队列,执行队列中保存的所有命令。队列中的命令全部执行完再集中全部返回给客户端,不是执行一条返回一条。
def EXEC():
# 创建空白的回复队列
reply_queue = []
# 遍历事务队列中的每个项
# 读取命令的参数,参数的个数,以及要执行的命令
for argv, argc, cmd in client.mstate.commands:
# 执行命令,并取得命令的返回值
reply = execute_command(cmd, argv, argc)
# 将返回值追加到回复队列末尾
reply_queue.append(reply)
# 移除REDIS_MULTI标识,让客户端回到非事务状态
client.flags & = ~REDIS_MULTI
# 清空客户端的事务状态,包括:
#1 )清零入队命令计数器
#2 )释放事务队列
client.mstate.count = 0
release_transaction_queue(client.mstate.commands)
# 将事务的执行结果返回给客户端
send_reply_to_client(client, reply_queue)
WATCH 命令的实现
WATCH命令是一个乐观锁,在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过,如果有,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。
eg:在时间T4,客户端B修改了"name"键的值,当客户端A在T5执行EXEC命令时,服务器会发现WATCH监视的键"name"已经被修改,因此服务器拒绝执行客户端A的事务,并向客户端A返回空回复。
使用WATCH 命令监视数据库键
每个Redis数据库都保存着一个watched_keys字典,字典的键是某个被WATCH命令监视的数据库键,字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端:
typedef struct redisDb {
// ...
// 正在被WATCH命令监视的键
dict *watched_keys;
// ...
} redisDb;
客户端为c10086,执行 WATCH "name" "age"
命令前后对比如下:
导致整个事务无法提交的情况
事务的ACID 性质
事务总是具有原子性、一致性和隔离性,并且当Redis 运行在某种特定的持久化模式下时,事务也具有耐久性。
原子性
要么就执行事务中的所有操作,要么就一个操作也不执行。
Redis的事务和传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制,即使事务队列中的某个命令在执行期间出现了错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。 注意以下两种情况除外:
1.由于事务入队的命令不存在,或者参数类型,参数个数不符合要求,整个事务将执行失败;
2.由于客户端监视的键被修改,造成事务的安全性被破坏,事务也不会执行;
一致性
- 入队错误:如果一个事务在入队命令的过程中,出现了命令不存在,或者命令的格式不正确等情况,那么Redis将拒绝执行这个事务。
- 执行错误:即使在事务的执行过程中发生了错误,服务器也不会中断事务的执行,它会继续执行事务中余下的其他命令,并且已执行的命令(包括执行命令所产生的结果)不会被出错的命令影响。
- 服务器停机:如果服务器运行在无持久化的内存模式下,那么重启之后的数据库将是空白的;如果服务器运行在RDB 模式下,服务器可以根据现有的RDB 文件来恢复数据;如果服务器运行在AOF模式下,根据现有的AOF文件来恢复数据。
隔离性
因为Redis使用单线程的方式来执行事务,所以Redis的事务总是以串行的方式运行的,并且事务也总是具有隔离性的。
耐久性
事务的耐久性指,当一个事务执行完毕时,执行这个事务所得的结果已经被保存到永久性存储介质里面了,即使服务器在事务执行完毕之后停机,执行事务所得的结果也不会丢失。