Redis事务与Watch机制相关知识点整理
Redis 事务
Redis事务提供了一种 “将多个命令打包, 然后一次性、按顺序地执行” 的机制,事务在执行的期间不会主动中断,服务器在执行完事务中的所有命令之后, 才会继续处理其他客户端的其他命令。
Redis 通过 MULTI
、 DISCARD
、 EXEC
和 WATCH
四个命令来实现事务功能。
MULTI: 标记着事务的开始,客户端从非事务状态切换到事务状态
EXEC: 执行事务队列中的命令
DISCARD: 取消一个事务,客户端从事务状态调整回非事务状态
WATCH: 在事务开始之前监视任意数量的键,当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了,那么整个事务不再执行直接返回失败。
- 基本事务
redis > multi
OK
redis > incr foo
QUEUED
redis > set t1 1
QUEUED
redis > exec
- Watch与事务结合
redis> WATCH name
OK
redis> MULTI
OK
redis> SET name peter
QUEUED
redis> EXEC
Redis 事务实现原理
事务从开始到执行三个阶段:
- 开始事务
- 命令入队
- 执行事务
MULTI
MULTI
命令会将客户端的 REDIS_MULTI 选项打开, 让客户端从非事务状态切换到事务状态。Redis的事务是不可嵌套的,所以当客户端已经处于事务状态再向服务器发送 MULTI
命令,服务器会向客户端发送一个错误后继续等待其他命令的入队。
OFFER(COMMAND) TO QUEUE
当客户端进入事务状态之后,服务器在接收该客户端命令时,不会立即执行,而是将这些命令全部放进一个事务队列里,然后返回 QUEUED
,表示命令已入队。事务队列是一个数组, 每一项是都包含三个属性:
- 要执行的命令(cmd)
- 命令的参数(argv)
- 参数的个数(argc)
Index | cmd | argv | argc |
---|---|---|---|
0 | SET | [“name”,“Python”] | 2 |
1 | GET | [“name”] | 1 |
2 | … | … | N |
EXEC
EXEC
命令执行时,如果当前客户端处于事务状态,服务器根据客户端所保存的事务队列, 以先进先出(FIFO)的方式执行事务队列中的命令,并将所得的结果以 FIFO
的顺序保存到一个回复队列中。当事务队列里的所有命令被执行完之后,会将回复队列作为自己的执行结果返回给客户端,此时客户端从事务状态返回到非事务状态。
Index | reply-type | reply-content |
---|---|---|
0 | status code reply | OK |
1 | bulk reply | “Python” |
2 | … | … |
执行流程如下所示:
DISCARD
DISCARD
命令用于取消一个事务,它清空客户端的整个事务队列,然后将客户端从事务状态调整回非事务状态, 最后返回字符串OK给到客户端说明事务已被取消。
WATCH
WATCH
命令用于在事务开始之前监视任意数量的键: 当调用 EXEC
命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。
- watched_keys字典
在每个代表数据库的 redis.h/redisDb 结构类型中, 都保存了一个 watched_keys
字典, 字典的键是这个数据库被监视的键, 而字典的值则是一个链表, 链表中保存了所有监视这个键的客户端。
WATCH
命令的作用, 就是将当前客户端和要监视的键在 watched_keys
中进行关联。
通过 watched_keys
字典, 如果程序想检查某个键是否被监视, 那么它只要检查字典中是否存在这个键即可; 如果程序要获取监视某个键的所有客户端, 那么只要取出键的值(一个链表), 然后对链表进行遍历即可。
- watch触发
在任何对数据库键空间(key space)进行修改的命令成功执行之后 (比如 FLUSHDB
、 SET
、 DEL
、 LPUSH
、 SADD
、 ZREM
…), multi.c/touchWatchedKey
函数都会被调用,它检查数据库的 watched_keys
字典, 检查是否有客户端在监视已经被命令修改的键, 如果有的话, 程序将所有监视这些被修改键的客户端的 REDIS_DIRTY_CAS
选项打开:
之后当客户端发送 EXEC
命令触发事务执行时, 服务器会对客户端的状态进行检查:
如果客户端的 REDIS_DIRTY_CAS
选项已经被打开,那么说明被客户端监视的键至少有一个已经被修改了,事务的安全性已经被破坏。服务器会放弃执行这个事务,直接向客户端返回空回复。
如果 REDIS_DIRTY_CAS
选项没有被打开,那么说明所有监视键都安全,服务器正式执行事务。
例如:
1. client3对 key1 进行了修改
2. 所有监视 key1 的客户端(client2,client5,client6)的 REDIS_DIRTY_CAS 选项都会被打开
3. 当client2,client5,client6执行 EXEC 的时候, 它们的事务都会失败
Redis事务与ACID
ACID事务定义
ACID 事务是指一组属性,旨在确保数据库事务的可靠性和一致性。术语 “ACID” 代表原子性、一致性、隔离性和持久性,这是 ACID 事务的四个关键属性。本质上,ACID 事务保证数据库操作正确执行,如果出现任何类型的故障,数据库可以恢复到以前的状态,而不会丢失任何数据或影响数据的一致性。
- Atomicity(原子性):保证事务被视为一个单一的、不可分割的工作单元。如果事务的任何部分失败,则必须回滚整个事务,这意味着事务期间所做的任何更改都将被撤消。这可确保数据库保持一致状态,而不管事务期间可能发生的任何故障。
- Consistency(一致性):一致性确保数据库在事务前后保持有效状态,即数据库模式必须满足所有约束和规则,任何违反这些约束的事务都必须回滚以保持数据库的一致性。确保了数据库保持其完整性并且数据保持准确和可靠。
- Isolation(隔离性):每个事务独立于其他事务运行,这意味着一个事务的效果只有在提交后才对其他事务可见。防止并发事务之间的干扰和冲突,维护数据库的完整性和一致性。
- Durability(持久性):确保即使在系统故障时,事务期间对数据库所做的更改也是不可逆的。
Atomicity(原子性)
Redis事务具备了一定的原子性,但不支持事务回滚机制:
- Redis 入队错误,如客户端向服务器发送了错误的命令,服务器将向客户端返回一个出错信息, 并且将客户端的事务状态设为
REDIS_DIRTY_EXEC
拒绝执行事务 ,保证原子性。 - Redis 队列执行错误,Redis只会将错误包含在事务的结果中,不会引起事务中断或整个失败,也不会影响已执行事务命令的结果。因为Redis 中并没有提供回滚机制,这种情况无法保证事务的原子性。
EXEC
命令执行时实例故障- AOF 模式:需要使用
redis-check-aof
工具检查 AOF 日志文件,把未完成的事务操作从 AOF 文件中去除,再使用 AOF 恢复实例后,事务操作不会再被执行,从而保证了原子性。 - RDB 模式:Redis 不会中断事务去执行保存 RDB 的工作,只有在事务执行之后,保存 RDB 的工作才有可能开始,所以事务内执行的命令,不管成功了多少,都不会被保存到 RDB 文件里,从而保证原子性。
- AOF 模式:需要使用
Consistency(一致性)
Redis事务具备了一致性。
- Redis 入队错误的情况,
REDIS_DIRTY_EXEC
拒绝执行事务,不会影响事务的一致性。 - Redis 队列执行错误的情况,不会引起事务中断或整个失败,也不会影响已执行事务命令的结果,所以它不会影响事务的一致性。
EXEC
命令执行时实例故障的情况- AOF 模式:使用
redis-check-aof
移除事务后进行还原,数据总是一致的,而且数据也是最新的(事务执行之前的数据),不会影响数据库一致性。 - RDB 模式:Redis 不会中断事务去执行保存 RDB 的工作,恢复数据库后的数据可能不是最新的,但只要RDB文件本身没有因为其他问题而出错,那么还原后的数据库就是一致的。
- AOF 模式:使用
Isolation(隔离性)
Redis事务具备了隔离性。
EXEC
命令执行时,因为Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务运行直到执行完所有事务队列中的命令为止。EXEC
命令执行前,通过 WATCH 机制保证隔离性。
Durability(持久性)
Redis事务不保证持久性。Redis事务没有提供任何额外的持久性功能,事务的持久性由 Redis 所使用的持久化模式决定:
- 如果 Redis 没有使用 RDB 或 AOF模式,事务的持久化无法保证。
- 如果 Redis 使用了RDB 模式,在一个事务执行后,RDB 快照还未执行前发生了故障,事务修改的数据也是不能保证持久化的。
- 如果 Redis 使用了AOF 模式,AOF 模式的 no、everysec 和 always 都会存在数据丢失的情况,亦不能保证持久化。
参考
https://redisbook.readthedocs.io/en/latest/feature/transaction.html