Redis事务学习笔记

Redis事务

先来一张思维导图
在这里插入图片描述

事务提供了一种“将多个命令打包,然后一次性的、按顺序的执行”的机制,并且事务在执行的期间不会主动中断,也就是说服务器在执行完事务中所有的命令后,才会继续处理其他客户端的其他命令

相关的命令

multi #开启一个事务
OK

# 事务逻辑命令

redis> set book-name "kkk"
QUEUED
redis> get book-name
QUEUED
redis> exec #事务的执行
1) OK
2) "kkk"

# 其他命令
redis> discard #事务的取消
redis> watch #在事务开始之前锁定任意数量的键

从上面的命令运行情况中,我们可以看出完成一个事务需要三步:

  1. 开始事务(multi)
  2. 命令入队 (相关命令)
  3. 执行事务

开启事务

multi命令执行代表着事务的开始。

该命令的作用就是,将客户端的redis_multi选项打开,让客户端从非事务状态切换到事务状态。

命令入队

当客户端处于非事务状态下时,所有发送给服务端的精灵都会立即被服务器执行,可是在事务状态下,服务器收到来自客户端的命令时,不会立即执行命令,而是将这些命令全部发进一个事务队列里,然后返回queued,表示命令已入队。

在这里,事务队列实际上是一个数组,每个数组项都包含了三个属性:

  1. 要执行的命令
  2. 命令的参数
  3. 参数的个数

执行事务

当客户端进入到事务状态后,客户端发送的命令就会被放到事务队列里,但除了exec,discard,multi,watch这四个命令,这四个命令会直接执行。

  1. 当exec执行时,服务器根据客户端所保存的事务队列,以先进先出(FIFO)的方式执行事务队列中的命令。

  2. 执行事务中的命令的结果同样会以FIFO的顺序保存到一个回复队列中。

  3. 当所有命令执行完毕后,exec会把回复队列作为自己的执行结果返回给客户端。并把redis_multi标识去除,客户端从事务状态返回到非事务状态。

事务状态和非事务状态执行命令的区别

  • 非事务状态下命令以单个命令为单位,前一个命令和后一个名字的客户端不一定是同一个;事务状态则是以一个事务为单位,执行事务队列中的所有命令,除非当前事务执行完毕,否则服务器不会主动中断事务,也不会执行其他客户端的其他命令。
  • 在非事务状态下,执行命令所得的结果会立刻返回客户端;而事务状态下则是所有命令的结果集合到回复队列,再做为exec命令的结果返回给客户端。

事务状态下的discard,multi,watch命令

  • discard用于取消一个事务,他清空客户端的整个事务队列,然后见客户端从事务状态调整回非事务状态,最后返回字符串ok给客户端,说明事务已取消。
  • redis事务是不可以嵌套的。所以当客户端处于事务状态,而客户端又发送一个multi时,服务器只是简单地向客户端发送一个错误,然后继续等待其他命令的入队。
  • watch只能在开启事务状态之前使用,如果客户端在事务状态下,客户端又发送了一个watch命令的话,服务器的处理跟重复发送multi一样。
  • 在事务状态开启时,发送multi和watch都只是引发一个错误,但不会造成整个事务失败,也不会修改事务队列中已有的数据;如果发生别的错误,比如发送一条不存在的命令的话,该事务就会abort。

带watch的事务

watch命令用于在事务开始之前见识任意数量的key,当调用exec的时候,被watch的keys如果被其他客户端修改了,那么整个事务不再执行,直接返回失败(nil)。

watch命令的实现(watch命令是一个乐观锁)

watched_keys的实现

在每个代表数据库的redis.h/redisDb数据结构中,都维护了一个watched_keys字典,字典的键是这个数据库被监视的键,而字典的值则是监控这个键的客户端链表,结构如图所示:

watch命令实际就是将被监视的key与当前客户端在watched_keys中进行关联。

Redis通过watched_keys字典,如果想检查某个键是否被监视,那么它只要检查字典中是否有这个键即可;如果进一步想要查看监视这个键的客户端,那么只需要取出值,然后遍历该链表即可。

watch的触发

在任何数据库键空间进行修改的命令执行成功之后(比如flushdb,set,del,lpush,sadd,zrem等),multi.h/touchWatchedKey函数都会被调用——它是用来检查watched_keys字典,看是否有客户端在监视已经被命令修改的键,如果有的话,程序将遍历该键的客户端链表,将这些客户端的redis_dirty_cas选项打开。

当客户端发送exec时,服务端首先会对客户端的状态进行检查:

  • 如果该客户端的redis_dirty_cas选项已经被打开,那么说明被客户端监视的键至少有一个已经被修改了,事务的安全性已经被破坏。服务器就会放弃这个事务的执行,直接返回空,表示事务执行失败。
  • 如果redis_dirty_cas选项没有被打开,那么说明所有监视键都安全,服务器正式执行事务。

watched_keys的移除

当一个客户端结束他的事务时,无论事务是否成功执行,watched_keys字典中和这个客户端相关的资料都会被清除。

对Redis的watch采用乐观锁的思考

像其他技术里的并发处理,都是把乐观锁放在一个初级的位置(一般会从乐观锁继续进行锁的升级),然后很多技术还积极使用悲观锁来保证用户高并发下的安全性,但为什么Redis仅仅采用了乐观锁的思路呢?

我觉得在于Redis是单线程的,不存在锁的问题,所以无法实现重量级锁。

事务的ACID特性

原子性

原子性指的是“多个操作当做一个整体来执行,要么都执行,要么都不执行”。

对于redis的事务功能来说,事务队列中的命令要么就全都执行,要么就一个都不执行。因此,redis事务是具有原子性的。

redis事务是依靠错误命令进入事务队列时发生错误而被服务器拒绝执行实现的,例子如下面:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name kkk
QUEUED
127.0.0.1:6379> get
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

redis事务与传统的关系型数据库事务的最大区别在于,Redis不支持事务回滚机制,即使事务队列中的某个命令在执行期出现了错误,整个事务也会继续执行下去,直到所有的事务队列中所有命令都执行完毕为止。举例如下面:

127.0.0.1:6379> set msg immsg
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name kkk
QUEUED
127.0.0.1:6379> rpush msg ll1 ll2
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) "kkk"

我们可以看到,即使rpush执行出错,事务的后续命令也会继续执行下去,并且之前执行的命令也不会有影响。

这里应该深刻理解原子性与回滚机制的关系。

原子性:多个操作作为一个整体执行,要么都执行,要么都不执行

回滚:事务若执行过程中出现错误,应该将数据库恢复到事务执行之前的状态

一致性

一致性指的是:如果数据库在执行事务前是一致的,那么在执行事务之后,无论事务是否执行成功,数据库也应该仍然是一致的。

这里的“一致”指的是数据符合数据库本身的定义和要求,没有包含非法或者无效数据。

Redis事务可能出错在三个地方,入队错误,执行错误,服务器停机,以下是对这三个错误对Redis事务的一致性影响的分析

入队错误

比如在入队时期,输入一条不存在的命令而发生了入队错误,当最后执行exec时,redis服务器会拒绝执行入队过程中出现错误的事务,所以redis事务的一致性不会被入队错误所影响。

by the way,redis在2.6.5以前是会执行发生过入队错误的事务的,只不过会跳过发生错误的入队命令,相当于错误命令没有进入事务队列,也是不会影响一致性。

执行错误

执行错误时在事务的执行期间发生的错误

  • 执行错误只会在命令实际执行时才会触发,入队时发现不了。类似于Java中编译期通过,执行期发生错误。
  • 执行错误发生后,事务不会中断,他会继续执行剩余命令,并且已执行的命令及其执行结果也不会被这个出错命令所影响。

执行错误会被服务器识别出来,并进行相应的错误处理,所以这些错误命令不会对数据库做任何修改,也就不会对事务的一致性产生任何影响。

服务器停机

无论是无持久化运行,还是aof持久化,还是rdb持久化运行,事务执行中途发生的停机都不会影响数据库的一致性。

隔离性

隔离性是指即使数据库中有多个事务并发的执行,各个事物之间也不会相互影响,并且在并发状态下执行的事务和串行执行的事务产生的结果完全相同。

因为Redis是单线程的,所以他以串行化的方式执行事务,并且服务器保证,在执行事务期间不会对事务进行中断,因此Redis的事务总是以串行化的方式运行的,所以其事务执行具有隔离性。

持久性

持久性指的是当事务执行完毕之后,执行这个事务所得的结果已经被保存到永久性存储介质了,即使服务器在事务执行完毕之后停机,执行事务所得到的结果也不会丢失。

Redis事务的持久性依赖于Redis是否采用持久化模式以及采用哪种持久化策略。

当且仅当Redis采用AOF持久化模式且采用always策略时,服务器会保存每一条修改的命令,这时候Redis事务具有持久性。

参考文章

  • redis设计与实现
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值