Redis事务的实现原理
Redis作为一个数据库使用时,它本身也提供了事务机制的支持。事务执行期间,Redis服务器不会去中断事务而执行其他客户端的命令请求,它会将事务中所有的命令都执行完毕之后,才去处理其他客户端的命令请求。Redis事务的实现主要通过MULTI
、EXEC
和WATCH
三个命令实现,其中MULTI
用于开启事务,EXEC
用于提交事务、WATCH
用于监视任意数量的key。
Redis事务实现的一个核心结构是事务队列,当服务器以事务状态运行时,针对于接收到的不同命令会有不同的操作:
- 如果是
MULTI
、EXEC
、WATCH
和DISCARD
其中的任意一个,服务器立刻执行 - 如果不是上述的四个命令,那么服务器就会将其放入到一个事务队列中,然后向服务器返回
QUEUED
恢复,表示命令已经入队,等待执行
其中,每个RedisClient
通过mstate
字段来标识自己的事务状态,而事务状态又包含一个事务队列和一个计数器,如下所示:
typedef struct redsiClient{
// ...
multiState mstate; // 事务状态
// ...
}
typedef struct multiState{
// ...
multiCmd *commands; // 事务队列
int count; // 入队命令计数器
// ...
}
typedeef struct multiCmd{
// ...
robj **args; // 参数
int argc; // 参数数量
struct redisCommand *cmd; // 命令指针
// ...
}
假设执行如下的Redis命令:
MULTI
set "name" "Kobe"
set "sport" "basktball"
get "name"
EXEC
当服务器使用MULTI开启事务后,后续所有除了MULTI
、EXEC
、WATCH
和DISCARD
之外的命令都会进入到事务队列中。当执行EXEC
时,服务器会遍历事务队列,执行队列中的所有命令,最后将命令执行的结果回复给客户端。
以事务形式执行上述的命令,如下所示:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name "Kobe"
QUEUED
127.0.0.1:6379> set sport "basketball"
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) "Kobe"
127.0.0.1:6379>
下面看一下WATCH
这个命令是如何参与事务控制的。WATCH
命令是一种乐观锁的实现方式,它在EXEC
命令执行之前监视任意数量的键,并在EXEC
执行期间检查监视的键是否至少有一个已经被修改。如果是,服务器拒绝执行事务,并向客户端返回代表事务执行失败的空回复。如果一个键都没有被修改,则成功执行事务。
WATCH
命令监视的键会保存在数据库的watched_keys
字典中,key为监视的键,value为监视该键的所有客户端,如下所示:
其中,所有监视键的客户端都会维护一个REDIS_DIRTY_CAS
标识,只要监视的键发生了改变,那么REDIS_DIRTY_CAS
就会被打开。Redis服务器在执行EXEC
时,只需要查看所有监视的键的客户端的REDIS_DIRTY_CAS
标识是否有至少一个被打开,只要有一个被打开,那么说明键被修改过,拒绝执行事务。
Redis事务的ACID
Redis所支持的事务同样满足事务的ACID,具体表现来说:
- 原子性:事务中的命令要么全部执行,要么一个都不执行。但Redis不支持事务的回滚,即使事务中包含出错的命令,也不影响其他正确命令的执行
- 一致性:Redis在各种可能出错的情况下都可以满足事务的一致性要求
- 隔离性:Redis事务总是串行执行,并且事务总是满足隔离性要求
- 持久性:Redis事务的持久性只有在支持AOF持久化,并且appendsync选项被配置为always时才能保证
总结
最后总结一下和事务相关的命令,如下所示:
命令 | 描述 |
---|---|
MULTI | 开启事务 |
EXEC | 执行事务 |
WATCH | 监视任意数量的key |
DISCARD | 取消事务,放弃执行事务块中的所有命令 |
UNWATCH | 取消WATCH对所有key的监视 |