关于Redis中的事务

989 阅读4分钟

Redis中通过MULTI,EXEC和WATCH命令来实现“类似事务”的功能,为什么说是“类似事务”,因为它跟我们对传统数据库的事务是有一定差别的

MULTI、EXEC命令

一个事务从开始到结束通常会经历三个阶段:

  1. 事务开始
  2. 命令入队
  3. 事务执行

事务开始

通过MULTI命令开启事务模式

当Redis服务器收到该命令时,会将Redis客户端的状态flag属性中标识REDIS_MULTI,用来标识该客户端开启了事务

命令入队

一般Redis服务器收到Redis客户端发送过来的命令都会立即执行,但是在Redis客户端开启事务后,Redis服务器不会立即执行该客户端发送过来的命令(事务命令除外),而是将命令暂存起来,放进一个队列结构

命令队列

每个Redis客户端都有自己的事务状态,事务状态包含一个事务队列,以及一个已入队命令的计数器

事务队列里的每个元素包含命令实现函数的指针,命令参数,参数的数量

事务执行

通过EXEC,服务器会立即遍历这个客户端的事务队列,按顺序执行队列中所有命令,最后将执行的结果全部返回给客户端

WATCH命令

通过上面的了解,其实只是将客户端发送的命令暂存起来,再一起执行,没有什么特别的。如果在命令队列的命令被执行前,有些键被其他客户端修改了,那么这个事务的安全性就被破坏了

WATCH命令就是丰富了这个过程的校验。它是一个乐观锁,可以在EXEC命令执行前,监视任意数量的数据库键,在执行EXEC时,检查被监视的键是否有被修改过,如果有则拒绝执行事务,并返回客户端执行失败的空回复

WATCH的实现原理

每个Redis数据库里都保存着一个watched_keys字典(key:被监视的数据库键,value:一个链表,记录了所有监视该键的客户端),通过这个字典就可以知道:

  1. 哪些键被监视
  2. 键被哪些客户端监视

当客户端通过WATCH命令监视某个键时,就会往这个字典对应key的value链表里面插入客户端信息

所有对数据库进行修改的命令,在执行之后都会调用multi.c/touchWatchKeys函数对watched_keys字典进行检查,查看是否有客户端监视的数据库键刚刚被修改了,如果有的话就会将客户端的REDIS_DIRTY_CAS标识打开,表示客户端的事务安全性已经被破坏了

事务的ACID性质

传统的关系型数据库中,常用ACID性质来校验事务的可靠性和安全性

这里尝试将ACID跟Redis中的事务进行匹配,因为感觉Redis并没有想要主动去实现ACID的事务

原子性

一般原子性指的是这些操作要么一起执行,要么一起不执行

在Redis的事务中,Redis将命令放入一个队列,然后按顺序去执行它,由于单线程的关系,执行的过程中也不会穿插进其他客户端执行的命令,它是满足一起执行的

但是Redis事务中没有回滚的操作,如果在执行命令的过程中有一条命令失败了,它还是会继续执行下去的,所以它不满足一起不执行的要求

所以不满足传统意义上的原子性

事务回滚这种复杂的功能与Redis追求简单高效的设计主旨不符,所以没有支持事务回滚

一致性

之前对一致性的理解是关于约束的一致性,类似A+B=100,如果一个事务改变了A,那么必须得改变B,使得事务结束后依然满足A+B=100,否则事务失败

由于Redis事务不满足传统的原子性,故也不满足一致性

隔离性

隔离性指事务并发执行的过程不会相互影响

由于Redis是单线程的方式来执行事务,因此事务是串行的方式执行的,因此具有隔离性

持久性

Redis事务的持久性其实是依赖于Redis服务器所使用的的持久化模式

  1. 当服务器在无持久化的内存模式下运行时,事务不具有持久性
  2. 当服务器在RDB模式下时,由于服务器只有在特定条件满足的情况才会执行BGSAVE命令进行持久化,并且该操作还是异步的,因此此模式下的事务也不具持久性
  3. 当服务器在AOF模式下时,并且appendfsync选项的值为always时,事务才具有持久性。如果appendfsync选项的值为evertsec或者no时,事务不具有持久性

参考

Redis设计与实现

本文使用 mdnice 排版