当前位置: 首页 > 后端技术 > Java

Redis事务是否支持ACID?

时间:2023-04-02 10:07:05 Java

腾讯面试官:“你了解数据库事务机制吗?”......非常自信和冷静地说话。腾讯面试官:“你了解Redis事务吗?它的事务机制能否实现ACID属性?”程旭源:“挠头,这……我知道lua脚本可以实现交易……”腾讯面试官:“好,回去等通知。”马哥,跟你学了《Redis 系列》,赢了一大堆offer,没想到被“Redis是如何实现事务的?”这个问题给打败了,下面一步步分析:什么是事务ACID?Redis是如何实现事务的?Redis事务可以实现哪些属性?Lua脚本实现,什么是业务?《ACID鬼吹灯》《云南虫谷》中的摩金队长有一句“合则生,分则死”的说法,为了找到牧尘珠,三人分工明确劳动和共同努力才能成功。事务(Transaction)是一个并发控制单元,是一系列操作的组合,这些操作要么执行要么不执行。“是一个不可分割的工作单元”。当一个事务被执行时,它将提供特殊的属性保证:原子性:一个事务的多个操作必须完成,否则一个都不完成(ps:MySQL是如何实现原子性的?欢迎在留言区评论);consistency一致性:事务执行后,不违反数据库的完整性约束,事务执行前后的顺序是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行主键的存在性和唯一性);列完整性(如字段的类型、大小、长度必须满足要求)外键约束;自定义完整性(比如转账前后,两个账户的余额之和应该保持不变)。隔离性:事务内的操作与其他事务隔离,并发执行的事务不能相互干扰。重要的是不同交易之间的交互。严格隔离对应隔离级别中的Serializable。持久性:事务一旦提交,所有修改都会永久保存在数据库中,即使系统崩溃重启,数据也不会丢失。码兄,了解了ACID的具体要求后,Redis是如何实现事务机制的呢?Redis如何实现事务MULTI、EXEC、DISCARD、WATCH命令是Redis实现事务的基础。Redis事务的执行过程包括三个步骤:开启一个事务;排队命令;执行交易或丢弃它;显式打开一个事务。实际执行。命令入队客户端将事务中要执行的一系列指令发送给服务器。需要注意的是,虽然命令被发送到服务器,但是Redis实例只是将这一系列命令暂时存储在一个命令队列中,并不会立即执行。执行事务或丢弃客户端向服务端发送提交或丢弃事务的命令,让Redis执行第二步发送的具体指令或清空队列命令,放弃执行。Redis可以安排排队的命令仅在调用EXEC时执行。第二步保存在队列中的命令也可以通过DISCARD来丢弃。Redis交易案例通过在线调试网站执行我们的示例代码:https://try.redis.io正常执行通过MULTI和EXEC执行一个交易过程:#开启交易>MULTIOK#开始定义一些系列指令>SET"公众号:码哥字节”“100万关注”QUEUED>SET“订单”“30”QUEUED>SET“文章数”666QUEUED>GET“文章数”QUEUED#实际执行交易>EXEC1)OK2)OK3)OK4)"666"我们看到每条读写命令的返回结果都是QUEUED,这说明感谢操作已经暂时存放在命令队列中,还没有真正执行,当执行EXEC命令时,可以查看每个具体命令的响应数据。通过MULTI和DISCARD放弃交易和丢弃队列命令:#初始化订单数>SET"order:mobile"100OK#开启交易>MULTIOK#Order-1>DECR"order:mobile"QUEUED#Discardthediscardedcolumncommand>DISCARDOK#ThedataisnotModify>GET"order:mobile""100"码哥,Redis事务能保证ACID特性吗?这是个好问题,我们一起来分析一下。Redis事务是否满足ACID?Redis事务可以同时执行多个命令,有以下三个重要保证:批处理指令在执行EXEC命令前会先放入队列暂存;收到EXEC命令后,进入事务执行,事务中任意一条命令执行失败,其余命令依然执行;在事务执行过程中,其他客户端提交的命令不会插入到当前命令执行序列中。原子代码兄,如果事务执行过程中出现错误,原子性能是否有保障?在交易过程中,可能会遇到两种命令错误:在执行EXEC命令之前发送的命令本身是错误的。如下:参数个数错误;命令名错误,使用了不存在的命令;内存不足(Redis实例使用maxmemory指令配置内存限制)。执行EXEC命令后,该命令可能会失败。例如命令和操作的数据类型不匹配(List列表操作是对String类型的值进行的);在执行事务的EXEC命令时。Redis实例失败,事务执行失败。EXEC执行前报错。当命令进入队列后,Redis会报错并记录错误。此时,我们可以继续提交命令操作。执行完EXEC命令后,Redis会拒绝执行所有提交的命令操作,并返回事务失败的结果。这样事务中的所有命令都不再执行,保证了原子性。下面是一个顺序入队错误,导致事务失败的例子:#Opentransaction>MULTIOK#Sendthefirstoperationinthetransaction,但是Redis不支持这个命令,返回错误信息127.0.0.1:6379>PUTorder6(error)ERRunknowncommand`PUT`,withargsbeginningwith:`order`,`6`,#Sendtransaction中的第二个操作,这个操作是正确的命令,Redisenqueuesthecommand>DECRb:stockQUEUED#实际执行了事务,但是之前的命令有错误,所以Redis拒绝执行>EXEC(error)EXECABORTTransactiondiscardedbecauseofpreviouserrors.EXEC执行后报错。事务操作入队时,命令和操作的数据类型不匹配,但是Redis实例没有检查出错误。但是,执行完EXEC命令后,Redis实际执行的是这些指令,会报错。敲黑板:虽然Redis会针对错误的命令报错,但是事务还是会执行正确的命令。这个时候交易的原子性是无法保证的!码哥,为什么Redis不支持回滚呢?事实上,Redis并没有提供回滚机制。虽然Redis提供了DISCARD命令。但是这个命令只能用于主动放弃事务执行,清空临时命令队列,没有回滚的效果。执行EXEC时,如果发生故障,如果Redis开启AOF日志,那么AOF日志中只会记录部分事务操作。我们需要使用redis-check-aof工具来检查AOF日志文件,可以将未完成的事务操作从AOF文件中剔除。这样,我们使用AOF恢复实例后,就不会再执行事务操作,从而保证了原子性。简单总结:命令入队时会报错,并放弃事务执行,保证原子性;命令入队时不会报错,真正执行命令时会报错,不保证原子性;当执行EXEC命令时实例失败,如果启用AOF日志,就可以保证原子性。一致性一致性会受到错误命令和实例失败时机的影响。根据命令错误和实例失败两个维度的时机,可以分三种情况进行分析。在EXEC执行之前,入队的报错事务会被丢弃,所以可以保证一致性。EXEC执行后,实际执行时会报错,错误的执行不会执行。正确的指令才能正常执行,保证一致性。执行EXEC时,实例失败后会重启实例。这与数据恢复的方式有关。我们会根据实例是否开启了RDB或者AOF来讨论。如果我们不开启RDB或者AOF,那么实例失败重启后,数据就没有了,数据库是一致的。如果我们使用RDBsnapshot,因为在执行事务时不会执行RDBsnapshot。因此,事务命令操作的结果不会保存在RDB快照中,使用RDB快照进行恢复时,数据库中的数据也是一致的。如果我们使用AOF日志,当事务操作还没有记录到AOF日志中时实例失败,那么使用AOF日志恢复的数据库数据是一致的。如果AOF日志中只记录了部分操作,我们可以使用redis-check-aof清除事务中已经完成的操作,恢复后数据库就一致了。隔离事务执行分为两个阶段:命令入队(EXEC命令执行前)和命令实际执行(EXEC命令执行后)。因此,在并发执行时,我们分两种情况来分析这两个阶段:并发操作在EXEC命令之前执行,需要通过WATCH机制来保证隔离性;并发操作在EXEC命令之后进行,可以保证隔离性。马哥,WATCH机制是什么?我们重点关注第一种情况:当一个事务的EXEC命令还没有被执行时,将该事务的命令操作暂存在命令队列中。这时候如果有其他并发操作修改同一个key,就要看事务是否使用了WATCH机制。WATCH机制的作用是在事务执行前监听一个或多个key的值变化。当事务调用EXEC命令执行时,WATCH机制会先检查被监控的key是否被其他客户端修改过。如果修改,则放弃事务执行,防止事务的隔离性被破坏。同时,客户端可以再次执行交易。此时如果没有并发修改事务数据的操作,事务可以正常执行,隔离性也有保障。WithoutWATCH如果没有WATCH机制,并发操作在执行EXEC命令之前读写数据。执行EXEC时,事务内部要操作的数据发生了变化,Redis没有做到事务间的隔离。并发操作在EXEC之后接收并执行对于第二种情况,由于Redis采用单线程执行命令,在执行完EXEC命令后,Redis会确保命令队列中的所有命令都执行完毕,然后再执行后续指令。因此,在这种情况下,并发操作不会破坏事务隔离。持久化如果Redis不使用RDB或者AOF,那么事务的持久化特性肯定得不到保证。如果Redis使用RDB模式,在一个事务执行后,下一个RDB快照执行前,如果实例宕机,数据丢失,这种情况下,事务修改的数据无法保证持久化的。如果Redis采用AOF模式,AOF模式的三个配置选项:no、everysec、always都会有数据丢失。因此,交易的持久性仍然得不到保证。无论Redis采用何种持久化模式,都无法保证事务的持久性。总结Redis具有一定的原子性,但不支持回滚。Redis没有ACID中一致性的概念。(或者Redis在设计上忽略了这一点)Redis是孤立的。Redis不能保证持久性。Redis的事务机制可以保证一致性和隔离性,但是不能保证持久化。但是,由于Redis本身是一个内存数据库,所以持久化并不是必须的属性。我们更关注原子性、一致性和隔离性这三个属性。原子性的情况比较复杂。当事务中使用的命令语法不正确时,无法保证原子性。在其他情况下,事务可以原子方式执行。好文推荐RedisCore:为什么这么快Redis持久化:AOF和RDB如何保证数据高可用Redis高可用:主从架构数据一致性同步原理Redis高可用:Sentinel集群原理Redis高可用:Cluster集群原理Redis实战:利用Bitmap实现亿级海量数据统计Redis实战:利用Geo类型实现附近人偶遇女神Redis新特性:多线程模型解读Redis6.0新特性:客户端缓存带来的革命