目录
[3.1 原子性保证](#3.1 原子性保证)
[3.2 交互次数](#3.2 交互次数)
[3.3 前后依赖](#3.3 前后依赖)
[3.4 流程编排](#3.4 流程编排)
一、Redis的事务机制
Redis中是支持事务的,他的事务主要目的是保证多个命令执行的原子性,即要在一个原子操作中执行,不会被打断。
需要注意的是,Redis的事务是不支持回滚的。从 Redis 2.6.5 开始,服务器将会在累积命令的过程中检测到错误。然后,在执行 EXEC 期间会拒绝执行事务,并返回一个错误,同时丢弃该事务。如果事务执行过程中发生错误,Redis会继续执行剩余的命令而不是回滚整个事务。
也就是说,如果一套事务的命令有语法错误,比如你写个压根没有的命令,那Redis会帮你检测到,并且整个事务的命令都不会执行。但要是执行过程中出错,比如对list进行incr操作,那还是会执行下面的命令,并且不会回滚。
redis其实挺尴尬的,像Mysql事务只要其中有一个命令出错,其它全不执行。但redis却不是这样,提供事务功能给你就不错了,凑合用吧,你程序员多认真点,尽量不要自己弄错就没事。如果真弄错了,那你程序员就自己手动回滚吧。
Redis事务相关的命令主要有以下几个:
- MULTI:标记一个事务块的开始。
- DISCARD:取消事务,放弃执行事务块内的所有命令。
- EXEC:执行所有事务块内的命令。
- UNWATCH:取消 WATCH 命令对所有 key 的监视。
- WATCH key [key ...]:监视一个(或多个)key,如果在事务执行之前这个(或这些)key 被其他命令所改动,那么事务将被打断。
二、什么是Redis的Pipeline?和事务有什么区别?
Redis 的 Pipeline 机制是一种用于优化网络延迟的技术,主要用于在单个请求/响应周期内执行多个命令。在没有 Pipeline 的情况下,每执行一个 Redis 命令,客户端都需要等待服务器响应之后才能发送下一个命令。这种往返通信尤其在网络延迟较高的环境中会显著影响性能。
在 Pipeline 模式下,客户端可以一次性发送多个命令到 Redis 服务器,而无需等待每个命令的响应。Redis 服务器接收到这批命令后,会依次执行它们并返回响应。
所以,Pipeline通过减少客户端与服务器之间的往返通信次数,可以显著提高性能,特别是在执行大量命令的场景中。
但是,需要注意的是,Pipeline是不保证原子性的。他的多个命令都是独立执行的,Redis并不保证这些命令可以以不可分割的原子操作进行执行。这是Pipeline和Redis的事务的最大的区别。
虽然都是执行一些相关命令,但是Redis的事务提供了原子性保障,保证命令执行以不可分割、不可中断的原子性操作进行,而Pipeline则没有原子性保证。
但是他们在命令执行上有一个相同点,那就是如果执行多个命令过程中,有一个命令失败了,其他命令还是会被执行,而不会回滚的。
三、Redis的事务和Lua之间有哪些区别?
Redis中,事务和Lua都是保证原子性的手段。当我们有多个命令要执行,并希望它们以原子性方式执行的时候,就会考虑使用事务或者Lua脚本。那么他们之间有哪些区别呢?
3.1 原子性保证
事务和Lua都是可以保证原子性操作的,但是,这里说的原子性我们提过很多次,指的是不可拆分、不可中断的原子性操作。所以,需要注意的是,不管是Redis的事务还是Lua,都没办法回滚,一旦执行过程中有命令失败了,都是不支持回滚的。
但是,Redis的事务在执行过程中,如果有某一个命令失败了,是不影响后续命令的执行的,而Lua脚本中,如果执行过程中某个命令执行失败了,是会影响后续命令执行的。
3.2 交互次数
在Redis的事务执行时,每一条命令都需要和Redis服务器进行一次交互。我们可以在Redis事务过程中,在MULTI和EXEC之间发送多个Redis命令给Redis服务器,这些命令会被服务器缓存起来,但并不会立即执行。但是每一条命令的提交都需要进行一次网络交互。
而Lua脚本则不需要,只需要一次性地把整个脚本提交给Redis即可。网络交互比事务要少。
3.3 前后依赖
在Redis的事务中,事务内的命令都是独立执行的,并且在没有执行EXEC命令之前,命令是没有被真正执行的,所以后续命令是不会也不能依赖于前一个命令的结果的。
而在Lua脚本中是可以依赖前一个命令的结果的。Lua脚本中的多个命令是依次执行的,我们可以利用前一个命令的结果进行后续的处理。
3.4 流程编排
借助Lua脚本,我们可以实现非常丰富的各种分支流程控制,以及各种运算相关操作。而Redis的事务本身是不支持这些操作的。
四、为什么Lua脚本可以保证原子性?
原子性在并发编程中和在数据库中是两种不同的概念。
在数据库中,事务的ACID中原子性指的是"要么都执行要么都回滚"。在并发编程中,原子性指的是"操作不可拆分、不被中断"。
Redis既是一个数据库,又是一个支持并发编程的系统,所以它的原子性有两种。因此,我们需要明确清楚,在问"Lua脚本保证Redis原子性"的时候,指的到底是哪个原子性。
Lua脚本可以保证原子性,因为Redis会将Lua脚本封装成一个单独的事务,而这个单独的事务会在Redis客户端运行时,由Redis服务器自行处理并完成整个事务。如果在这个进程中有其他客户端请求的时候,Redis将会把它暂存起来,等到Lua脚本处理完毕后,才会再把被暂存的请求恢复。
这样就可以保证整个脚本是作为一个整体执行的,中间不会被其他命令插入。但是,如果命令执行过程中命令产生错误,事务是不会回滚的,将会影响后续命令的执行。
也就是说,Redis保证以原子方式执行Lua脚本,但是不保证脚本中所有操作要么都执行或者都回滚。
那就意味着,Redis中Lua脚本的执行,可以保证并发编程中不可拆分、不被中断的这个原子性,但是没有保证数据库ACID中要么都执行要么都回滚的这个原子性。
五、为什么Redis不支持回滚?
我们都知道,Redis是不支持回滚的,即使是Redis的事务和Lua脚本,在执行的过程中,如果出现了错误,也是无法回滚的。可是,为什么呢?
在Redis的官网文档中明确提到过,不支持回滚: Transactions | Docs
不支持回滚主要的原因是支持回滚将对 Redis 的简洁性和性能产生重大影响。
++总结一下,因为Redis的设计就是简单、高效等,所以引入事务的回滚机制会让系统更加复杂,并且影响性能。从使用场景上来说,Redis一般都是被用作缓存的,不太需要很复杂的事务支持。当人们需要复杂的事务时会考虑持久化的关系型数据库。相比于关系型数据库,Redis是通过单线程执行的,在执行过程中,出现错误的概率比较低,并且这些问题一般在编译阶段都应该被发现,所以就不太需要引入回滚机制。++