原创

Redis学习笔记(六)——redis事务详解


一、Redis事务介绍

1. 什么是事务

事务可以一次执行多个命令,且有如下两个重要的保证

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

2. Redis中的事务

Redis 通过 MULTIDISCARDEXECWATCH四个命令来实现事务功能,先简单介绍一下上述四个命令的基本意思:

  • MULTI:用于开启一个事务
  • DISCARD:执行后表示放弃事务
  • EXEC:执行队列中的所有命令
  • WATCH:为 Redis 事务提供 check-and-set (CAS)行为

二、Redis事务用法

1. 开启事务

  • MULTI命令的执行标志着事务的开始,它总是返回 OK

    127.0.0.1:6379> MULTI
    OK
    
  • 该命令做的事就是将客户端的REDIS_MULTI选项打开,让客户端从非事务状态切换到事务状态

2. 命令入队

  • 当客户端处于非事务状态时,所有发送给客户端的命令都会被服务器立即执行

  • 而当客户端处于事务状态时,服务器收到来自客户端的命令时不会立即执行,而是将命令放入事务队列中去,然后返回QUEUED

    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> get k1
    QUEUED
    
  • 事务队列

    • 本质是一个数组,包含三个属性

      • 1)要执行的命令(cmd)
      • 2)命令的参数(argv)
      • 3)参数的个数(argc)
    • 例如上面的三行命令,redis程序将为客户端创建以下事务队列

      数组索引要执行的命令(cmd)命令的参数(argv)参数的个数(argc)
      0SET["k1","v1"]2
      1SET["k2","v2"]2
      2SET["k3","v3"]2
      3GET["k1"]1

3. 执行事务

  • 前面聊到,当客户端处于事务状态后,客户端发送的命令就会被放到事务队列中

  • 当然并不是所有的命令都会被放入事务队列中去,其中例外的就是 MULTIDISCARDEXECWATCH这四个命令,当这些命令发送到服务器时,服务器会立即执行对应的指令并返回结果

  • MULTI:如果客户端处于事务状态,那么执行MULTI命令时,服务器会根据客户端保存的事务队列按照先进先出原则顺序执行命令;执行结束后将返回一个回复队列给客户端(顺序同事务队列中的命令一致),客户端将从事务状态返回到非事务状态,至此,事务执行完毕。如上的执行将返回如下结果:

    127.0.0.1:6379> EXEC
    1) OK
    2) OK
    3) "v1"
    
  • 如果执行过程中某一个或多个命令报错(注意是执行过程报错,非加入到事务队列报错,如果是加入队列就报错,整个操作全部失败),不影响其余命令,即其他命令将继续执行,不受影响。如下,其中INCR k1/k2操作将会报错,但是不影响其他命令。

    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> INCR k1
    QUEUED
    127.0.0.1:6379> set k2 vv
    QUEUED
    127.0.0.1:6379> INCR k2
    QUEUED
    127.0.0.1:6379> set k1 kk
    QUEUED
    127.0.0.1:6379> EXEC
    1) (error) ERR value is not an integer or out of range
    2) OK
    3) (error) ERR value is not an integer or out of range
    4) OK
    127.0.0.1:6379> get k1
    "kk"
    127.0.0.1:6379> get k2
    "vv"
    

4. 取消事务

  • DISCARD :表示取消事务,执行后将清空整个事务队列,然后将客户端从事务状态调整回非事务状态,最后返回OK,表示事务已被取消。

5. 带 WATCH 的事务

  • WATCH

    • 用于事务开始之前监视任意数量,当调用EXEC命令执行事务时,如果任意一个被监视的键其他客户端修改了,那么整个事务不再执行,直接返回失败
    • WATCH可以执行多次,对键的监视从WATCH执行之后开始生效,直到调用EXEC为止,且不管事务是否成功执行,对所有键的监视都会被取消
    • 另外当客户端断开时,该客户端对键的监视也会被取消
  • UNWATCH:使用无参数的该命令可以手动取消对所有键的监视

  • WATCH命令的实现:

    • 在每个代表数据库的redis.h/redisDb结构类型中,都保存了一个watch_keys字典,字典的键是这个数据库被监视的键,而字典的值则是一个链表,链表保存了所有监视这个键的客户端。如下图所示:

    • WATCH命令就是将当前客户端和要监视的键在watch_keys中进行关联。例如:客户端client007执行命令watch key1 key2 时,上述的watch_keys将被修改成如下所示:

  • WATCH命令的触发:

    • 任何对数据库键空间的修改命令执行之后(例如FLUSHDBSETDELLPUSHSADDZREM等),都会调用函数multi.c/touchWatchedKey它会检查数据库的watch_keys字典,查看是否有客户端在监视以及被命令修改的键,如果有,则会将所有监视这些被修改的键的客户端的REDIS_DIRTY_CAS 选项打开
    • 当客户端执行EXEC命令时,服务器会对客户端的状态进行检查:
      • 如果客户端的``REDIS_DIRTY_CAS选项已经被打开,那么说明被客户端监视的键至少有一个已经被修改了,事务的安全性已经被破坏。服务器会放弃执行这个事务,直接向客户端返回空回复,表示事务执行失败。
      • 如果客户端的REDIS_DIRTY_CAS `选项没有打开,那么表示所有监视的键都安全,服务器正常执行事务

三、Redis事务与ACID关系

相对于传统关系型数据库的ACID性质,Redis事务保证了其中的一致性(C)和隔离性(I),但是并不保证原子性(A)和持久性(D),下面将针对这四种性质详解介绍:

1. 原子性(Atomicity)

  • 单个redis命令的执行是原子性的,但是在事务上(含有多个操作命令下),redis并没有增加任何维持其原子性的机制,故redis事务的执行不具备原子性。
  • 即使事务失败,redis也不会进行任务的重试或者回滚操作

2.一致性(Consistency)

Redis 的一致性问题可以分为以下三部分来讨论

  • 1)入队错误

    • 在命令入队的过程中,如果客户端向服务器发送了错误的命令,比如命令的参数数量不对, 那么服务器将向客户端返回一个出错信息, 并且将客户端的事务状态设为 REDIS_DIRTY_EXEC
    • 当客户端执行 EXEC 命令时, Redis 会拒绝执行状态为 REDIS_DIRTY_EXEC 的事务, 并返回失败信息。
    • 因此带有不正确入队命令的事务不会被执行,也不会影响数据库的一致性。
  • 2)执行错误

    • 如果在命令执行的过程中发生了错误(例如给一个字符串类型的值自增操作),redis只会将事务的报错信息加入到事务的结果队列中,并不会引起事务的中断或整个失败,不会影响事务的执行结果,更不会影响后面要执行的命令,即对事务一致性无影响。
  • 3)Redis进程被终结

    如果 Redis 服务器进程在执行事务的过程中被其他进程终结,或者被管理员强制杀死,那么根据 Redis 所使用的持久化模式,可能有以下情况出现:

    • 内存模式:由于未采取任何持久化机制,故重启之后的数据库总是空白,即数据总是一致
    • RDB模式:
      • 在执行事务时,Redis 不会中断事务去执行保存 RDB 的工作,只有在事务执行之后,保存 RDB 的工作才有可能开始。
      • 所以当 RDB 模式下的 Redis 服务器进程在事务中途被杀死时,事务内执行的命令,不管成功了多少,都不会被保存到 RDB 文件里。
      • 恢复数据库需要使用现有的 RDB 文件,而这个 RDB 文件的数据保存的是最近一次的数据库快照(snapshot),所以它的数据可能不是最新的,但只要 RDB 文件本身没有因为其他问题而出错,那么还原后的数据库就是一致
    • AOF模式:因为保存 AOF 文件的工作在后台线程进行,所以即使是在事务执行的中途,保存 AOF 文件的工作也可以继续进行,因此,根据事务语句是否被写入并保存到 AOF 文件,有以下两种情况发生
      • 1)如果事务语句未写入到 AOF 文件,或 AOF 未被 SYNC 调用保存到磁盘,那么当进程被杀死之后,Redis 可以根据最近一次成功保存到磁盘的 AOF 文件来还原数据库,只要 AOF 文件本身没有因为其他问题而出错,那么还原后的数据库总是一致的,但其中的数据不一定是最新的。
      • 2)如果事务的部分语句被写入到 AOF 文件,并且 AOF 文件被成功保存,那么不完整的事务执行信息就会遗留在 AOF 文件里,当重启 Redis 时,程序会检测到 AOF 文件并不完整,Redis 会退出,并报告错误。需要使用redis-check-aof工具将部分成功的事务命令移除之后,才能再次启动服务器。还原之后的数据总是一致的,而且数据也是最新的(直到事务执行之前为止)。

3. 隔离性(Isolation)

  • Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。

4. 持久性(Durability)

因为事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能,所以事务的持久性由 Redis 所使用的持久化模式决定

  • 在单纯的内存模式下,事务肯定是不持久的。
  • RDB 模式下,服务器可能在事务执行之后、RDB 文件更新之前的这段时间失败,所以 RDB 模式下的 Redis 事务也是不持久的。
  • AOF 的“总是 SYNC ”模式下,事务的每条命令在执行成功之后,都会立即调用 fsyncfdatasync 将事务数据写入到 AOF 文件。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。
  • 其他 AOF 模式也和“总是 SYNC ”模式类似,所以它们都是不持久的。

四、Redis事务总结

  • 事务提供了一种将多个命令打包,然后一次性、有序地执行的机制。
  • 事务在执行过程中不会被中断,所有事务命令执行完之后,事务才能结束。
  • 多个命令会被入队到事务队列中,然后按**先进先出(FIFO)**的顺序执行。
  • WATCH 命令的事务会将客户端和被监视的键在数据库的 watched_keys 字典中进行关联,当键被修改时,程序会将所有监视被修改键的客户端的 REDIS_DIRTY_CAS 选项打开。
  • 只有在客户端的 REDIS_DIRTY_CAS 选项未被打开时,才能执行事务,否则事务直接返回失败。
  • Redis 的事务保证了 ACID 中的一致性(C)隔离性(I),但并不保证原子性(A)和持久性(D)。

参考

Redis
NoSQL
  • 作者:贤子磊
  • 发表时间:2020-02-26 18:07
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 评论

    您需要登录后才可以评论