一、AOF
操作:数据先写入内存,然后将命令记录日志(文本形式)Redis 在 AOF 里面记录日志时,不会对命令进行语法检查。
好处
- 在执行命令的时候已经检查过,先执行命令后写日志的好处,可以避免出现记录错误命令的情况
- 不会阻塞当前的写操作
潜在风险
- 如果执行完一个命令,没有来得及写日志就宕机,就会丢数据。
- AOF 日志也是在主线程中执行,虽然避免对当前命令的阻塞,但可能会给下一个操作带来阻塞风险
风险来源:AOF 写回磁盘的时机有关。可以控制一个命令执行完后 AOF 日志写回磁盘的时机就可以解除风险
AOF 三种写回策略
- Always,同步写回:每个命令执行完,立马同步将日志写回磁盘
- Everysec:每秒写回:每个命令执行完,只是先把日志写到AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘
- NO:操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
配置项 | 写回时机 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 可靠性高,数据基本不丢失 | 每个写命令都要落盘,性能影响较大 |
Everysec | 每秒写回 | 性能适中 | 宕机时丢失1秒内的数据 |
No | 操作系统控制的写回 | 性能好 | 宕机时丢失数据较多 |
风险点:写命令越来越多,AOF 文件过大带来的性能问题。
- 文件系统本身对文件大小有限制,无法保存过大的文件
- 如果文件过大,之后再往里面追加命令记录的话,效率变低
- 如果发生宕机,AOF中记录的命令要一个个被执行,用于故障恢复,文件太大,恢复过程越长
AOF 重写机制
AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件,也就是说,读取数据库中所有的键值对,然后对每一个键值对用一条命令记录它的写入。(把操作同一个键值对的写入命令合并成一个)
重写过程是由后台子进程 bgrewriteaof 来完成。此处有一次拷贝和两处日志。
- “一次拷贝”指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志
- 第一处日志指正在使用的AOF日志,Redis 会把这个操作写到它的缓冲区,即使宕机了,这个AOF日志的操作仍然是齐全的,可以用于恢复
- 第二处日志指新的AOF重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后(根据数据库里数据的最新状态,生成这些数据的插入命令),重写日志记录的这些最新操作也会写入新的AOF文件,以保证数据库最新状态的记录。此时,就可以用新的AOF文件替代旧文件了
问题一:
AOF 日志重写的时候,是由 bgrewriteaof 子进程来完成的,不用主线程参与,我们今天说的非阻塞也是指子进程的执行不阻塞主线程。但是,你觉得,这个重写过程有没有其他潜在的阻塞风险呢?如果有的话,会在哪里阻塞?
- fork 子进程,fork 采用写实拷贝机制,但是fork 子进程需要拷贝进程必要的数据结构(进程控制块,简称PCB(Process Control Block)),其中有一项是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝消耗CPU,拷贝完成之前整个进程会阻塞,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。
- fork 出的子进程指向与父进程相同的内存地址空间,此时子进程就可以执行AOF重写,此时父进程如果操作已经存在的key,就会真正拷贝这个key对应的内存数据,申请新的内存空间。因为内存分配是以页为单位进行分配的,默认4K,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产生阻塞风险。另外,如果操作系统开启了内存大页机制(2M),那么父进程申请内存的阻塞的概率将会大大提高,所以Redis机器一般需要关闭 Huge Page 机制。Redis 每次 fork 生成 RDB 和 AOF 重写完成后,都可以在 Redis log 中看到父进程重新申请了多大的内存空间。且操作系统在分配内存空间时,有查找和锁的开销。
问题二:
AOF 重写也有一个重写日志,为什么它不共享使用 AOF 本身的日志呢?
- 父子进程写同一个文件必然产生竞争问题,控制竞争就意味着影响父进程的性能
- 如果AOF 重写过程失败了,那么原本的AOF 文件相当于污染了,无法做恢复使用。而AOF 重写一个文件失败的话直接删除这个文件即可,不会影响原来的AOF文件。
二、RDB
内存快照:内存中的数据在某一时刻的状态记录,且为全量快照。
Redis 提高两个命令来生成RDB文件:
- save:在主线程中执行,会导致阻塞
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免主线程的阻塞。默认配置
bgsave 的操作:主线程 fork 出 bgsave 子进程,bgsave 子进程开始读取主线程的内存数据,并把他们写入 RDB 文件。如果Redis 收到写请求,主线程会复制数据副本并修改。既保证了快照的完整性,也允许主线程同时对数据进行修改,避免对正常业务的影响。
快照的间隔时间太短,会带来很大开销;快照的间隔时间太长,有可能丢数据
- 频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始了,容易造成恶性循环
- bgsave 子进程需要通过fork操作从主线程创建。fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。
增量快照:做了一次全量快照后,后续的快照只对修改的数据进行快照记录。需要记录哪些数据被修改,会带来额外的空间开销。
三、混合AOF日志和内存快照
内存快照以一定的频率执行,两次快照之间,使用AOF 日志记录这期间的所有命令操作(Redis 4.0 提出)
此方案快照不用频繁执行,避免fork 对主线程的影响,而且,AOF 日志也只用两次快照间的操作,不需要记录所有操作,AOF文件也不会很大,避免重写开销。
关于AOF和RDB的选择
- 数据不能丢失时,内存快照和AOF的混合使用是很好的选择
- 如果允许分钟级别的数据丢失,可以只使用RDB
- 如果只用AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡