当前位置: 首页 > 科技观察

腾讯二面:Redis事务是否支持ACID?

时间:2023-03-21 13:09:43 科技观察

商务的ACID是什么?《云南虫谷》中的莫金队长有句话叫“合则生,分则亡”。可以成功。事务(Transaction)是一个并发控制单元,是一系列操作的组合,这些操作要么执行要么不执行。“是一个不可分割的工作单元”。事务在执行时,会提供特殊的属性保证:原子性:一个事务的多个操作必须完成,或者不完成(ps:MySQL的原子性是如何实现的?一致性:事务执行后,事务的完整性约束数据库没有被破坏,事务执行的顺序是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一);列完整性(如字段的类型、大小、长度必须满足要求)外键约束;自定义完整性(如转账前后,两个账户的余额之和要保持不变)隔离:事务内的操作与其他事务是隔离的,并发执行的事务不能相互干扰,重要的是它们之间的相互影响不同的交易。严格隔离对应隔离级别中的Serializable。持久性:事务一旦提交,所有修改都会永久保存在数据库中,即使系统崩溃重启,数据也不会丢失。码兄,了解了ACID的具体要求后,Redis是如何实现事务机制的呢?Redis如何实现事务MULTI、EXEC、DISCARD和WATCH命令是Redis实现事务的基础。Redis事务的执行过程包括三个步骤:开启事务;命令排队;执行交易或丢弃;通过MULTI命令显式开启一个事务客户端显式表示开启一个事务,后续命令会被排队缓存,不会真正执行。命令队列客户端向服务器发送事务中要执行的一系列指令。需要注意的是,虽然指令是发送给服务器的,但是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#丢弃被丢弃的列command>DISCARDOK#数据还没有被修改>GET"order:mobile""100"码哥,Redis事务能保证ACID特性吗?这是个好问题,我们一起来分析一下。Redis事务满足ACID?Redis事务可以同时执行多个命令,有以下三个重要保证:批处理指令在执行EXEC命令前会先放入队列暂存;收到EXEC命令后,进入事务执行,事务中任意事务命令执行失败,其余命令仍会执行;在事务执行过程中,其他客户端提交的命令不会插入到当前命令执行序列中。原子代码兄,如果在事务执行过程中出现错误,原子性能是否有保障?在交易过程中,可能会遇到两种命令错误:EXEC命令执行前,命令本身错误。如下:参数个数错误;命令名错误,使用了不存在的命令;内存不足(Redis实例使用maxmemory指令配置内存限制)。执行EXEC命令后,该命令可能会失败。例如命令和操作的数据类型不匹配(对String类型的值进行List操作);在执行事务的EXEC命令时。Redis实例失败,事务执行失败。EXEC执行前报错。当命令进入队列后,Redis会报错并记录错误。此时,我们可以继续提交命令操作。执行完EXEC命令后,Redis会拒绝执行所有提交的命令操作,并返回事务失败的结果。这样事务中的所有命令都不再执行,保证了原子性。下面是命令入队错误导致事务失败的例子:#OpenTransaction>MULTIOK#Send事务中的第一个操作,但是Redis不支持这个命令,返回错误信息127.0.0.1:6379>PUTorder6(error)ERRunknowncommand`PUT`,withargsbeginningwith:`order`,`6`,#发送事务中的第二个操作,这个操作是正确的命令,Redis将命令入队>DECRb:stockQUEUED#实际执行事务,但是之前的命令有Error,所以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日志,就可以保证Atomicity。一致性一致性会受到错误命令和实例失败时机的影响。根据命令错误和实例失败两个维度的时机,可以分三种情况进行分析。在EXEC执行之前,入队的报错事务会被丢弃,所以可以保证一致性。EXEC执行后,实际执行时会报错,错误的执行不会执行。正确的指令才能正常执行,保证一致性。执行EXEC时,实例失败后会重启实例。这与数据恢复的方式有关。我们会根据实例是否开启了RDB或者AOF来讨论。如果我们不开启RDB或者AOF,那么实例失败重启后,数据就没有了,数据库是一致的。如果我们使用RDBsnapshot,因为在执行事务时不会执行RDBsnapshot。因此,事务命令操作的结果不会保存在RDB快照中,使用RDB快照进行恢复时,数据库中的数据也是一致的。如果我们使用AOF日志,当事务操作还没有记录到AOF日志中时实例失败,那么使用AOF日志恢复的数据库数据是一致的。如果AOF日志中只记录了部分操作,我们可以使用redis-check-aof清除事务中已经完成的操作,恢复后数据库就一致了。隔离事务执行分为两个阶段:命令入队(EXEC命令执行前)和命令实际执行(EXEC命令执行后)。因此,在并发执行时,我们分两种情况来分析这两个阶段:并发操作在EXEC命令之前执行,需要通过WATCH机制来保证隔离性;并发操作在EXEC命令之后进行,可以保证隔离。Code兄,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本身是一个内存数据库,所以持久化并不是必须的属性。我们更关注原子性、一致性和隔离性这三个属性。原子性的情况比较复杂。当事务中使用的命令语法不正确时,无法保证原子性。在其他情况下,事务可以原子方式执行。本文转载自微信公众号《码哥字节》