Chapter 19 - 事务
Created by : Mr Dk.
2020 / 06 / 10 17:04
Nanjing, Jiangsu, China
Redis 通过几条特殊的命令来实现事务。事务能够将多条命令请求打包,然后一次性、按顺序地执行。在事务执行期间,服务器不会中断事务执行其它客户端的命令请求。当事务中的请求执行完毕后,才回去处理其它客户端的请求。
Implementation
事务由一个 MULTI
命令为开始,接着将多个命令放入事务中,最终由 EXEC
命令将这个事务提交给服务器执行,对应了三个阶段:
MULTI
命令对应了事务的 开始 - 打开了客户端状态中 flag 属性的REDIS_MULTI
标识- 客户端处于事务状态,服务器将普通命令存入 事务队列,然后向客户端返回
QUEUED
EXEC
命令对应了事务的 执行,服务器遍历客户端的事务队列,依次执行其中保存的命令
在服务器上维护的客户端状态中,有一个事务状态结构体:
typedef struct redisClient {
// ...
multiState mstate;
// ...
} redisClient;
事务状态结构体中维护了 事务队列 及其长度:
typedef struct multiState {
multiCmd *commands; // 事务队列
int count; // 已入队命令数
} multiState;
事务队列中的每个元素对应了事务中的一条命令及其参数,其定义如下:
typedef struct multiCmd {
robj **argv; // 参数
int argc; // 参数数量
struct redisCommand *cmd; // 指向命令结构
} multiCmd;
Implementation of WATCH
WATCH
命令用于维护事务的安全性。它可以在 EXEC
命令执行前 (即事务实际执行前) 对任意多个 key 进行监视,如果被监视的 key 至少有一个被修改,那么服务器将拒绝执行事务。从而保证事务的执行是安全的。
Redis 数据库中维护了一个 watched_keys
字典:
typedef struct redisDb {
// ...
dict *watched_keys;
// ...
} redisDb;
这个字典的 key 就是数据库中被监视的 key;value 是一个链表,维护了监视这个 key 的全部客户端。所有对数据库进行修改的命令,如:
SET
LPUSH
SADD
ZREM
DEL
FLUSHDB
- ...
这些命令执行后,Redis 都会调用 touchWatchKey()
函数 对 watched_keys
字典进行检查。如果修改的 key 位于字典中,那么就将监视这个 key 的所有客户端的 REDIS_DIRTY_CAS
标志位打开,表示客户端的事务安全性已经被破坏。
当服务器接收到来自客户端的 EXEC
命令时,根据客户端状态中的 REDIS_DIRTY_CAS
来决定是否执行当前事务。如果这个标志位已被打开,那么事务的执行不再安全,服务器拒绝为这个客户端执行事务。
ACID
传统的数据库中讲究事务的 ACID 四个特性。Redis 中的事务能满足这四个特性吗?
原子性
事务中的操作要么不执行,要么全部执行。因此 Redis 的事务功能是具有原子性的。
与传统关系型数据库的区别在于,Redis 不支持 回滚。事务队列中的某个命令在执行时发生了错误,那么整个事务也会继续执行下去,直到所有的命令都执行完毕。
Redis 不支持事务回滚是因为与 Redis 追求简单高效的设计主旨不符。Redis 的作者认为事务的执行错误通常都是编程错误产生的。
一致性
事务执行前,数据库是一致的;事务执行后,无论成功失败,数据库也应该是一致的。一致指的是数据库符合本身的定义和要求,不包含非法或无效的数据。Redis 事务可能出错的地方包含:
- 入队错误 - 加入事务队列的命令不存在,或命令格式不正确,Redis 拒绝执行这个事务
- 执行错误 - 特指在入队时无法被发现的错误,只能在命令实际运行时被触发;出错命令不会引发对数据库的修改
- 服务器停机 - 如果没有持久化,那么重启后的数据库将是空白的;如果有持久化,服务器从 RDB 或 AOF 文件中恢复数据库状态
由于 Redis 对上述三种错误都进行了妥善的处理,因此一致性能够得到满足。
隔离性
多个并发事务执行,事务之间不会相互影响,并发状态下执行的事务与串行状态下执行的事务结果相同。
由于 Redis 以单线程的方式执行各个事务 (以及事务中的各条命令),并且服务器不会在事务执行期间中断事务。因此 Redis 中的事务总是串行执行且隔离的。
持久性
当事务执行完毕时,结果应当已经保存到永久性存储介质中了。由于 Redis 基于内存,且没有为事务提供额外的持久化功能,因此 Redis 的事务持久性由 Redis 本身使用的持久化模式决定。
其中,只有 AOF 模式下的 appendfsync
选项为 always
时,命令的执行结果才能立刻写入磁盘中;其它模式下,Redis 都要等一定的条件满足才会进行持久化。因此,只有这一种配置下的事务具有持久性。
配置选项 no-appendfsync-on-rewrite
会停止对 AOF 文件进行同步,即使 appendfsync
的选项为 always
。这样一来,即使是这种配置下,事务也不在具有持久性。