19-事物


Redis 通过 MULTI 、 EXEC、WATCH等命令来实现事务( transaction )功能.
以下是一个事务执行的过程. 该事务首先以一个 MULTI命令为开始, 接着将多个命令放人事务当中, 最后由 EXEC 命令将这个事务提交( commit )给服务器执行:
在这里插入图片描述

1 事务的实现

一个事务从开始到结束通常会经历以下三个阶段:
1 )事务开始。
2) 命令人队。
3) 事务执行。

事务开始

MULTI命令的执行标志着事务的开始:MULTI命令可以将执行该命令的客户端从非事物状态切换至事物状态,通过在客户端状态的flags属性中打开REDIS_MULTI标识来完成的

命令入队

当一个客户端处于非事务状态时, 这个客户端发送的命令会立即被服务器执行:
当一个客户端切换到事务状态之后, 服务器会根据这个客户端发来的不同命令执行不同的操作:
在这里插入图片描述

事务队列

每个Redis客户端都有自己的事务状态, 这个事务状态保存在客户端状态的mstate属性里面:
在这里插入图片描述

事务状态包含一个事务队列, 以及一个已人队命令的计数器(也可以说是事务队列的长度):
在这里插入图片描述

事务队列是一个multiCmd类型的数组, 数组中的每个multiCmd结构都保存了一个已入队命令的相关信息, 包括指向命令实现函数的指针、 命令的参数, 以及参数的数量:
在这里插入图片描述

事务队列以先进先出( FIFO )的方式保存入队的命令, 较先人队的命令会被放到数组的前面, 而较后人队的命令则会被放到数组的后面。

执行事务

当一个处于事务状态的客户端向服务器发送 EXEC 命令时, 这个EXEC 命令将立即被服 务器执行。 服务器会遍历这个客户端的事务队列, 执行队列中保存的所有命令, 最后将执行命令所得的结果全部返回给客户端。

WATCH命令的实现

WATCH命令是一个乐观锁( optimistic locking ), 它可以在 EXEC 命令执行之前, 监视任意数量的数据库键, 并在 EXEC 命令执行时, 检查被监视的键是否至少有一个已经被修改过了, 如果是的话, 服务器将拒绝执行事务, 并向客户端返回代表事务执行失败的空回复。
本节接下来的内容将介绍WATCH 命令的实现原理, 说明事务系统是如何监视某个键, 并在键被修改的情况下, 确保事务的安全性的。

使用WATCH 命令监视数据库键

每个 Redis 数据库都保存着一个 watched_keys 字典, 这个字典的键是某个被WATCH命
令监视的数据库键, 而字典的值则是一个链表,链表中记录了所有监视相应数据库键的客户端:
通过 watched keys 字典, 服务器可以清楚地知道哪些数据库键正在被监视, 以及哪些客户端正在监视这些数据库键。
图 19-3 是一个 watched keys 字典的示例, 从这个 watched keys 字典中可以看出:
口客户端 cl 和 c2 正在监视键 ” name” 。
口客户端 c3 正在监视键 ” age ” 。
口客户端 c2 和 c4 正在监视键 ” address ” 。
在这里插入图片描述

通过执行WATCH 命令, 客户端可以在 watched_keys 字典中与被监视的键进行关联。
举个例子, 如果当前客户端为 c10086,那么客户端执行以下WATCH命令之后:
在这里插入图片描述

图 19-3 展示的 watched keys 字典将被更新至图 19-4 所示的状态, 其中用虚线包围的两个 cl0086 节点就是由刚刚执行的WATCH命令添加到字典中的。
在这里插入图片描述

监视机制的触发

所有对数据库进行修改的命令,比如SET、LPUSH、SADD、ZREM、DEL、FLUSHDB等等,在执行之后都会调用multi.c/touchWatchKey函数对watchedkeys字典进行检查,查看是否有客户端正在监视刚刚被命令修改过的数据库键,如果有的话,那么touchWatchKey 函数会将监视被修改键的客户端的 REDIS DIRTY CAS 标识打开, 表示该客户端的事务安全性已经被破坏
在这里插入图片描述
当服务器接收到一个客户端发来的 EXEC 命令时, 服务器会根据这个客户端是否打开了REDIS DIRTY CAS标识来决定是否执行事务:
在这里插入图片描述

事务的ACID性质

在Redis中, 事务总是具有原子性(Atomicity)、 一致性(Consistency )和隔离性
( Isolation ), 井且当Redis运行在某种特定的持久化模式下时, 事务也具有耐久性(Durability)。

原子性

事务具有原子性指的是, 数据库将事务中的多个操作当作一个整体来执行, 服务器要么执行事务中的所有操作, 要么就一个操作也不执行。
Redis的事务 是具有原子性的。
Redis的事务和传统的关系型数据库事务的最大区别在于, Redis不支持事务回滚机制( rollback ), 即使事务队列中的某个命令在执行期间出现了错误, 整个事务也会继续执行下去, 直到将事务队列中的所有命令都执行完毕为止。

一致性

Redis 通过谨慎的错误检测和简单的设计来保证事务的一致性, 以下三个小节将分别介绍三个 Redis 事务可能出错的地方, 并说明 Redis 是如何妥善地处理这些错误, 从而确保事务的一致性的。

1.入队错误
如果一个事务在人队命令的过程中, 出现了命令不存在, 或者命令的格式不正确等情况, 那么 Redis 将拒绝执行这个事务。

2.执行错误
除了人队时可能发生错误以外, 事务还可能在执行的过程中发生错误。
关于这种错误有两个需要说明的地方:
口执行过程中发生的错误都是一些不能在入队时被服务器发现的错误, 这些错误只会在命令实际执行时被触发。
口即使在事务的执行过程中发生了错误, 服务器也不会中断事务的执行(不回滚), 它会继续执行事务中余下的其他命令, 并且已执行的命令(包括执行命令所产生的结果)不会 被出错的命令影响。

3.服务器停机
如果Redis服务器在执行事务的过程中停机, 那么根据服务器所使用的持久化模式, 可能有以下情况出现:
口如果服务器运行在无持久化的内存模式下, 那么重启之后的数据库将是空白的, 因此数据总是一致的。
口如果服务器运行在RDB/AOF模式下, 那么在事务中途停机不会导致不一致性,因为服务器可以根据现有的 RDB /AOF文件来恢复数据, 从而将数据库还原到一 一致的状态。 如果找不到可供使用的 RDB /AOF文件, 那么重启之后的数据库将是空白的, 而空白数据库总是一致的。

隔离性

事务的隔离性指的是, 即使数据库中有多个事务并发地执行, 各个事务之间也不会互相影响, 并且在并发状态下执行的事务和串行执行的事务产生的结果完全相同。
因为 Redis 使用单线程的方式来执行事务(以及事务队列中的命令), 并且服务器保证, 在执行事务期间不会对事务进行中断, 因此, Redis 的事务总是以串行的方式运行的, 并且事务也总是具有隔离性的。

耐久性

事务的耐久性指的是, 当一个事务执行完毕时, 执行这个事务所得的结果已经被保存到永久性存储介质(比如硬盘 )里面了, 即使服务器在事务执行完毕之后停机, 执行事务所得 的结果也不会丢失。
因为 Redis 的事务不过是简单地用队列包裹起了一组 Redis 命令, Redis 并没有为事务
提供任何额外的持久化功能
, 所以 Redis 事务的耐久性由 Redis 所使用的持久化模式决定:

口当服务器运行在AOF持久化模式下, 并且appendfsync=no时, 程序会交由操作系统来决定何时将命令数据同步到硬盘。 因为事务数据可能在等待同步的过程中丢失, 所以这种配置下的事务不具有耐久性

只有AOF+appendfsync=always时,具有持久性

在这里插入图片描述
不论Redis 在什么模式下运作, 在一个事务的最后加上 SAVE 命令总可以保证事务的耐久性:
不过因为这种做法的效率太低 , 所以并不具有实用性。

重点回顾

在这里插入图片描述

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页