Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • ⛸️ Redis Implementation
    • Part 1 - 数据结构与对象

      • Chapter 2 - 简单动态字符串
      • Chapter 3 - 链表
      • Chapter 4 - 字典
      • Chapter 5 - 跳跃表
      • Chapter 6 - 整数集合
      • Chapter 7 - 压缩列表
      • Chapter 8 - 对象
    • Part 2 - 单机数据库的实现

      • Chapter 9 - 数据库
      • Chapter 10 - RDB 持久化
      • Chapter 11 - AOF 持久化
      • Chapter 12 - 事件
      • Chapter 13 - 客户端
      • Chapter 14 - 服务器
    • Part 3 - 多机数据库的实现

      • Chapter 15 - 复制
      • Chapter 16 - Sentinel
      • Chapter 17 - 集群
    • Part 4 - 独立功能的实现

      • Chapter 18 - 发布与订阅
      • Chapter 19 - 事务
      • Chapter 21 - 排序
      • Chapter 22 - 二进制位数组
      • Chapter 23 - 慢查询日志

Chapter 19 - 事务

Created by : Mr Dk.

2020 / 06 / 10 17:04

Nanjing, Jiangsu, China


Redis 通过几条特殊的命令来实现事务。事务能够将多条命令请求打包,然后一次性、按顺序地执行。在事务执行期间,服务器不会中断事务执行其它客户端的命令请求。当事务中的请求执行完毕后,才回去处理其它客户端的请求。

Implementation

事务由一个 MULTI 命令为开始,接着将多个命令放入事务中,最终由 EXEC 命令将这个事务提交给服务器执行,对应了三个阶段:

  1. MULTI 命令对应了事务的 开始 - 打开了客户端状态中 flag 属性的 REDIS_MULTI 标识
  2. 客户端处于事务状态,服务器将普通命令存入 事务队列,然后向客户端返回 QUEUED
  3. 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 事务可能出错的地方包含:

  1. 入队错误 - 加入事务队列的命令不存在,或命令格式不正确,Redis 拒绝执行这个事务
  2. 执行错误 - 特指在入队时无法被发现的错误,只能在命令实际运行时被触发;出错命令不会引发对数据库的修改
  3. 服务器停机 - 如果没有持久化,那么重启后的数据库将是空白的;如果有持久化,服务器从 RDB 或 AOF 文件中恢复数据库状态

由于 Redis 对上述三种错误都进行了妥善的处理,因此一致性能够得到满足。

隔离性

多个并发事务执行,事务之间不会相互影响,并发状态下执行的事务与串行状态下执行的事务结果相同。

由于 Redis 以单线程的方式执行各个事务 (以及事务中的各条命令),并且服务器不会在事务执行期间中断事务。因此 Redis 中的事务总是串行执行且隔离的。

持久性

当事务执行完毕时,结果应当已经保存到永久性存储介质中了。由于 Redis 基于内存,且没有为事务提供额外的持久化功能,因此 Redis 的事务持久性由 Redis 本身使用的持久化模式决定。

其中,只有 AOF 模式下的 appendfsync 选项为 always 时,命令的执行结果才能立刻写入磁盘中;其它模式下,Redis 都要等一定的条件满足才会进行持久化。因此,只有这一种配置下的事务具有持久性。

配置选项 no-appendfsync-on-rewrite 会停止对 AOF 文件进行同步,即使 appendfsync 的选项为 always。这样一来,即使是这种配置下,事务也不在具有持久性。

Edit this page on GitHub
Prev
Chapter 18 - 发布与订阅
Next
Chapter 21 - 排序