
1. MySQL 在写缓存和日志之前就已经将 SQL 语法校验过,且 InnoDB 的 redo log 是物理日志(在某个数据页上修改了哪些数据);Redis 的 AOF 日志里记录的是命令,而 Redis 为了避免额外检查的开销,选择先执命令,执行失败的命令就不记录日志,执行成功才会记录,这样就会避免记录错误的命令日志。
数据丢失:当刚执行完一个命令,还没来得及记录日志就发生宕机,数据就会丢失。
性能影响:AOF 日志的执行也是在主线程里,势必对吞吐量会有影响。
为了有针对性的解决「数据丢失」和「性能影响」问题,Redis 提供三种 AOF 回写策略:Always、Everysec、No;Linux 操作系统中为了提升性能,使用了页缓存(page cache),在执行完 Redis 命令后,Redis 会将日志先写入叫做 page cache 的内存缓冲区,当调用操作系统内核的 fsync 方法时才会真正写入磁盘,而 AOF 的这三种写回策略决定着 fsync 的时机。
Always:同步写回,每个命令执行完写入 page cache 后,立刻调用 fsync 方法写入磁盘;这种模式可靠性比较高数据基本不会丢失,但每次有写命令都会同步到磁盘,会影响 Redis 整体的吞吐量;
No:操作系统控制写回时机,命令执行完后,只是把命令写入 AOF 文件的内存缓冲区(page cache),由操作系统控制何时落盘;这种模式性能较好,同步磁盘的操作交给操作系统后台线程,一般为 30 s,同步时机不可控,丢失的数据量也难以估计,数据非常不安全;
Everysec:每秒写回,命令执行完后,先把日志写到 AOF 文件的内存缓冲区,然后每隔一秒将缓冲区的内容同步到磁盘。这种模式算是 Always 和 No 两种模式的折中方案:每秒落盘一次,避免了同步写回的开销,同时保证最多只丢失一秒的写命令;一般推荐使用该种模式控制 AOF 的写回时机。
AOF 重写
随着时间的推移,AOF 文件会越来越大,当文件过大就会导致一些性能问题:
1. 文件过大后,再往里追加记录的话效率会变低,整体吞吐量也会受到影响;
2. 发生宕机后,AOF 持久化模式下 Redis 的恢复是需要从头到尾执行整个 AOF 文件的,文件越大恢复速度也会越慢。
由于是对整个 Redis 的内存数据生成命令,这个过程会非常耗时,自然不会交给主线程做,当发生 AOF 重写时,Redis 会 fork 出来一个子进程来执行重写操作,避免阻塞主线程。
AOF 重写逻辑主要分为两步,fork + 双写:
1. 主线程首先 fork 出子进程,子进程复制父进程的虚拟页表;
2. 子进程开始读取内存中的数据,写到新的 AOF 重写日志中;
3. 子进程重写日志后,主进程还是会接收写命令,写命令除了会正常写入 AOF 旧文件的缓冲区,还会写到写到重写日志的缓冲区,保证重写的过程中发生宕机,旧的 AOF 日志还是全的;
4. 当子进程完成重写后,将重写缓冲区的增量命令刷新到重写日志文件中,保证新写入的数据不丢,增量日志的刷盘完成后就会将新的 AOF 文件代替旧文件了。
子进程为什么要拷贝父进程的虚拟页表而不是直接拷贝数据?
其实 fork 子进程这个过程是会阻塞主进程的,fork 采用写时复制(Copy On Write)机制,写时复制的思想就是当发生写的时候才去复制,防止一次性拷贝大量数据造成主线程阻塞过长和内存浪费问题,主进程会拷贝一份虚拟内存页表(虚拟内存地址和物理内存地址映射)给子进程,子进程就可以和主进程共享访问同一块物理内存区域,进行数据的读取重写工作;重写过程中如果主进程接收到某个 key 的写请求时,会拷贝这个 key 所在的内存页数据到新申请的内存页上,然后在新的内存页上进行更新,后续等到重写完成替换掉旧内存页。
RDB 生成策略
Redis 会根据配置自动进行 RDB 快照的生成,默认保存在 dump.rdb 的二进制文件中。而 RDB 生成策略配置格式如下:save t n,这个配置的语义是:当满足“在 t 秒内数据集发生 n 个改动”时就会触发一次 RDB 的生成;比如下面这个配置:
save 10 100
复制
代表当 10 秒内有 100 次写入就触发一次 RDB 的生成。
save 60 10000
save 300 10
save 900 1
复制
RDB 过程
在满足配置自动进行 RDB 生成时,Redis 同样会 fork 出子进程来进行持久化(不阻塞主进程),也是采用 COW 写时复制机制,子进程将数据写入临时文件中,完成后替换旧的 RDB 文件;在生成 RDB 的过程中,主线程可以处理读写请求。
RDB 优点:
1. 直接存储内存数据的二进制序列化形式,占用磁盘更小。
2. 宕机后重启恢复数据的速度比 AOF 快。
RDB 缺点:
1. 数据安全性较低,发生宕机时最多会丢失两次快照之间的命令。
2. RDB 持久化的执行也是 Redis fork 出子进程执行,同样是重量级操作,频繁执行成本较高。
三. 混合持久化
那有没有一种既有 AOF 的较高数据安全性也有 RDB 快速恢复数据能力的方式呢?
config set aof-use-rdb-preamble yes
复制
本文介绍 Redis 两种基本持久化方式:AOF 与 RDB,使用 AOF 的数据安全性更高,但是宕机重启数据恢复速度较慢;RDB 的恢复速度快,但是快照生成频率不好控制;在 Redis 4.0 提出了混合使用 AOF 和 RDB 的方式,既有 AOF 的数据安全性 又有 RDB 的恢复速度,官方也建议如果需要持久化就用混合的方式。我们知道在 AOF 重写和 RDB 生成的时候,为了不阻塞主线程,Redis 采用了 写时复制(COW)机制,这种方式如果过程中出现大量写操作(内存页覆盖面较广)就会频繁出现写时复制,当此时内存不足就会发生 OOM,所以在使用 Redis 时,官方建议预留一部分的内存空间防止出现这个问题。


《Redis 核心技术与实战》作者:蒋德钧
https://mp.weixin.qq.com/s/v4z9tUm46mUN4SxxMwwP6A
https://www.zhihu.com/question/57045322
https://redis.io/docs/management/persistence