Redis 持久化与事务特性

2022-09-20 11:02:18 浏览数 (1)

1、持久化

AOF

append-only-file只追加写,这个机制是对操作信息进行记录,其中记录的信息是以命令的形式存在,宕机之后恢复会根据AOF文件的命令进行执行恢复,这就是重放。AOF的过程是在主线程执行的,因此在Redis中是先执行命令再写入日志,这可以避免错误的命令被写入,也可以避免在执行命令之前对主线程的阻塞。

AOF的记录实际上不是直接写文件的,因为这会导致IO异常高,因此每次执行命令后都是将命令序列化到内核为文件描述符提供的缓存中,之后根据策略进行文件写入。写回策略有三种,appendfsync always代表每次执行命令后都会进行文件写入,会阻塞下一个命令执行,性能较低;appendfsync no 代表由操作系统决定何时写回文件;appendfsync everysec 代表每一秒进行aof文件写入,是对上面两种的折中。

当Redis运行久了,AOF就会变得很臃肿,这时候就需要进行瘦身操作,也即是AOF重写。AOF重写实际上是一个多变一的过程,假设在原来的AOF文件中记录的是对key的多次修改,那么实际上在当前我们只需要它的最后一次修改情况,因此AOF重写就可以将这多条命令转换为根据数据库现有状态重建的那一条命令,也就实现了瘦身。其原理就是fork出来一个新的进程,复制主进程的页表(虚实映射关系),进而在子进程中将内存中的数据转换为命令形式,开始执行AOF记录写入新的AOF文件,执行完毕用新的AOF文件替换旧的即可。

因为重写的过程主线程仍在运行,也就是还可能会有命令的执行发生,因此这时需要考虑同步的情况。在AOF重写过程中,如果有命令发生,会将命令同步记录到旧AOF缓存以及新AOF缓存中,这样可以很大程度上保证命令不丢失。

RDB

将数据库中某时刻的键值对信息按照格式写入到rdb文件中,因此这里的RDB是一种快照机制,只是保存下执行SAVE时刻的数据库信息,在发生宕机之后会执行RDB恢复机制,其优点是恢复时直接读取文件数据即可,恢复速度比AOF快。需要注意的是RDB执行的是全量快照,即使改动的是一个key值,也是需要全量写入数据库信息,同时SAVE是会阻塞进程执行的,因此可以使用BGSAVE创建一个子进程来执行。

这时候要考虑做快照时能不能修改数据的问题了,如果不允许修改数据,那么在快照做完之前都不能执行命令,显然是不可行的,因此需要考虑边持久化边处理操作,这时候采用的是操作系统提供的写时复制(COW)功能。RDB在做快照的时候会fork出来一个bgsave子进程,与父进程共享内存数据,bgsave子进程会读取内存数据,写入RDB文件中。这时候如果对主线程中某块数据进行了修改,那么会将这块内存拷贝一个副本,之后bgsave子进程读取到副本中的内容,写入RDB文件中,此时主线程还是可以正常执行其他操作。

解决了怎么做快照,还应该考虑何时写快照。

既然是全量快照,那就不能每写入一个键值就开始,不然这开销大的离谱,一个是磁盘写入的压力,一个是fork进程的开销。那能不能一秒一次快照呢?也不能,这样的话出现宕机可能会丢失这一秒的快照信息,持久化得不到保障。可以考虑增量快照的方法,每次只快照修改的数据部分,但这仅凭RDB是做不到的,也就需要用到下面的混合持久化了。

混合持久化

上面提到了AOF可以记录增量的信息,RDB是全量的快照,那么如果我们需要增量的快照,实际上可以将这两个做一下融合,RDB以一定的频率去执行,期间发生的增量数据用AOF来记录,这样除了可以避免频繁fork的开销,还可以避免AOF文件过大的情况。这种情况下RDB文件和AOF文件存在一起,这里的AOF只是增量的日志,而RDB依旧是全量的,Redis重启时会先从RDB恢复,之后再从AOF恢复。

2、事务

ACID详细写一下

A:原子性:要求一个事务是不可再分的,要么全部发生,要么全部不发生;

C:一致性:要求事务执行前后不改变数据库的状态,例如不应该执行后把错误的数据写入其中;

I:隔离性:事务A在未提交之间做的修改对于事务B应该是不可见的,不能读到互相未提交的内容;

D:持久性:要求数据是可持久化的,也即是数据是不易丢失的;

Redis中事务的开启使用的是MULTI,提交使用的是EXEC,在这两个命令之间的命令都会被存入一个命令队列中,只有提交后才会依次执行。

与MySQL不同的是,在入队的时候Redis就会对命令的语法格式进行校验,如果是不合法的语句就会报错,提交时所有命令都会失败;此外Redis并不会对命令语句的合法性做判断,例如对String键进行列表操作是不会在入队时报错的,只会在提交时报错,但其他命令会正常执行。

关于原子性,如果从整体上看,Redis的事务中的命令都是在队列中取出来依次执行的,那么原子性应该是可以保障的,但如果是出现语句操作不恰当,那么其他正常语句还是会执行,并且Redis中并没有回滚的机制,这就会给人一种不原子的感觉。我觉得从广义上来看,Redis的事务是支持原子性的,因为其作者也提到过,对于这些语句上的错误(非语法)在客户端传入的时候就应该检查,而不是依赖于Redis的检查,因此Redis也选择不提供回滚机制,减轻负担。如果从这方面来讲,只要保证了命令操作是正确的,那么事务的原子性实际上也是可以保证的,只是并非数据库的原子概念。

关于一致性,由于命令在入队以及执行时都会有相应的检查机制,因此也不会造成不一致的情况,一致性是可以保证的。

关于隔离性,可以使用WATCH启用对键的监控,如果检测到该键的变更,那么该事务在提交时就会整体失败,并且由于命令执行是单线程的,队列中的命令都可以保证依次执行,因此在这种环境下的隔离性是可以保证的。

关于持久性,Redis提供RDB和AOF的持久化方案,对于RDB来说,并不是在每条命令执行时都进行记录,而是根据设置的自动save时间以及手动save来进行快照的写入;对于AOF来说,三种方案都有造成数据丢失的风险,因此无论是哪一种方案实际上都无法完全保证持久性。

0 人点赞