流水线
流水线操作有助于客户端向服务器发送多个请求,而无需等待回复,最后只需一步即可读取回复。减少了客户端和Redis服务器网络通信的次数。
![20230425152851](https://liubing-1314895948.cos.ap-chengdu.myqcloud.com/img/202304251529161.png)
Redis事务的特性
复习:DBMS的ACID
- Atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交、读提交、可重复读和串行化。
- Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
Redis事务是一组命令的集合,将多个命令进行打包,然后这些命令会被顺序的添加到队列中,并且按顺序的执行这些命令。Redis事务中没有像Mysql关系型数据库事务隔离级别的概念,不能保证原子性操作,也没有像Mysql那样执行事务失败会进行回滚操作。这个与Redis的特点:快速、高效有着密切的关联,因为一些列回滚操作、像事务隔离级别那这样加锁、解锁,是非常消耗性能的。
流水线与事务
流水线与事务虽然在概念上有些相似,但是在作用上却并不相同:流水线的作用是将多个命令打包,然后一并发送至服务器,而事务的作用则是将多个命令打包,然后让服务器一并执行它们。
因为Redis的事务在EXEC命令执行之前并不会产生实际效果,所以很多Redis客户端都会使用流水线去包裹事务命令,并将入队的命令缓存在本地,等到用户输入EXEC命令之后,再将所有事务命令通过流水线一并发送至服务器,这样客户端在执行事务时就可以达到“打包发送,打包执行”的最优效果。
事务的实现
Redis事务相关命令:
- MULTI : 开启事务,redis会将后续的命令逐个放入队列中,并不会立即执行,然后使用EXEC命令来原子化执行这个命令系列。
- EXEC: 执行事务中的所有操作命令,并返回所有命令的执行结果。
- DISCARD: 取消事务,放弃执行事务块中的所有命令。
![1](https://liubing-1314895948.cos.ap-chengdu.myqcloud.com/img/202304251619765.png)
事务的异常
1. 语法错误
![image-20230425164135613](https://liubing-1314895948.cos.ap-chengdu.myqcloud.com/img/202304251641653.png)
由于incrby命令在放入队列之前就被检测到有缺少参数的语法错误,所以第三条事务命令并不会执行,并且其他事务命令都不能正常执行。
2. 执行异常
![image-20230425163509249](https://liubing-1314895948.cos.ap-chengdu.myqcloud.com/img/202304251635313.png)
由于incrby命令并没有缺少参数(没有严格意义上的语法错误),可以将该命令放入队列中。却在执行的过程中出现异常(incrby的操作数只能是整数),结果就是该异常的事务命令不会被执行,但是其他正常的事务命令可以被执行,不会发生回滚。
事务隔离机制
1. 命令
- WATCH:监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
- UNWATCH:取消WATCH对所有key的监视。
2. 隔离实现原理
- 当某一客户端对 key 执行了 watch 后,系统就会为该 key 添加一个 version 乐观锁,并初始化 version。例如初值为 1.0。
-
此后某个客户端 A 将对该 key 的修改语句写入到了事务命令队列中,虽未执行,但其将该key 的 value 值与 version 进行了读取并保存到了当前客户端缓存。此时读取并保存的是version 的初值 1.0。
-
此后客户端 B 对该 key 的值进行了修改,这个修改不仅修改了 key 的 value 本身,同时也增加了 version 的值,例如使其 version 变为了 2.0,并将该 version 记录到了该 key信息中。
-
此后客户端 A 执行 exec,开始执行事务中的命令。不过,其在执行到对该 key 进行修改的命令时,该命令首先对当前客户端缓存中保存的 version 值与当前 key 信息中的version 值。如果缓存 version 小于 key 的 version,则说明客户端缓存的 key 的 value 已经过时,该写操作如果执行可能会破坏数据的一致性。所以该写操作不执行。
3. 隔离实现
- 客户端A得到source的数量为50,于是对source进行监控,并开启事务。开启事务后,将source的数量减少40。
![image-20230425180137794](https://liubing-1314895948.cos.ap-chengdu.myqcloud.com/img/202304251801835.png)
- 在客户端A执行事务之前,客户端B将source的数量减少30,并成功返回。
![image-20230425180212577](https://liubing-1314895948.cos.ap-chengdu.myqcloud.com/img/202304251802607.png)
- 此时,当客户端A执行事务的时候返回结果为nil,表示事务执行失败。因为在客户端A执行事务之前,Redis检测到source的数量已经改变,因此执行失败。