【每日八股】Redis篇(三):持久化(上)
目录
- AOF 和 RDB?
- AOF
- RDB
- AOF-RDB 混用
- AOF 的三种写回策略?
- AOF 的磁盘重写机制?
- 为什么要先执行 Redis 命令,再把数据写入 AOF 日志呢?
- AOF 的重写的具体过程?
- AOF 子进程的内存数据跟主进程的内存数据不一致怎么办?
- RDB 在执行快照期间,数据可以修改吗?
AOF 和 RDB?
Redis 提供两种持久化机制,分别是 RDB(Redis Database)和 AOF(Append-Only File),二者各有特点:
AOF
每执行一条写操作命令,就将该命令以追加的方式写入到 AOF 文件,在恢复时,以逐一执行命令的方式来进行数据恢复。用 AOF 日志的方式来恢复数据很慢,因为 Redis 执行命令由单线程负责,AOF 日志恢复数据的方式是顺序执行日志当中的每一条命令,如果 AOF 日志很大,这个过程会很慢。
RDB
RDB 快照是记录每一瞬间的内存数据,记录的是实际数据(而 AOF 文件记录的是命令操作的日志,而不是实际的数据)。在 Redis 恢复数据时,RDB 恢复数据的效率比 AOF 更高,因为使用 RDB 恢复数据时,直接将 RDB 文件读入内存就可以,不需要像 AOF 那样额外执行操作命令的步骤才能恢复数据。
RDB 是全量快照,即每次执行快照,都是把内存中所有数据都记录到磁盘中。如果频率太频繁,可能对 Redis 的性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。通常设置至少 5 分钟进行一次快照保存,也就是 Redis 宕机时,最多丢失 5 分钟数据。
AOF-RDB 混用
Redis 的 AOF-RDB 混合持久化模式(需要 Redis 4.0+)是一种结合两种持久化优势的创新性方案,它采用双格式存储,具体来说是使用 RDB 头部和 AOF 增量命令来构成 AOF 文件。在重启 Redis 加载数据时,先加载 RDB 数据块,再重放后续的 AOF 命令,使得 Redis 数据可以快速恢复。
AOF 的三种写回策略?
分别是 Always、Everysec 和 No。这三种策略在可靠性上由高到低,性能上从低到高。
- Always:每次写操作命令执行后,同步将 AOF 日志数据写回磁盘;
- Everysec:每次写操作命令完成后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区当中的内容写回到磁盘;
- No:不控制写回磁盘的时机。每次写操作完成后,先将命令写入 AOF 文件的内核缓冲区,由 OS 决定何时落盘。
AOF 的磁盘重写机制?
随着执行的命令越来越多,AOF 文件的体积会越来越大,为了避免日志文件过大(即解决 Redis 日志文件膨胀问题),Redis 提供了 AOF 重写机制。
重写的目的是消除 AOF 日志当中冗余的命令(例如多次修改同一键的值,旧的修改命令将不会生效)以及压缩 AOF 文件的体积(生成等效但更紧凑的新的 AOF 文件)。
具体来说,重写的流程如下。首先,主进程 fork 一个子进程,子进程利用写时复制技术,避免阻塞主进程。之后,子进程遍历数据库,将当前数据状态转为等效的最小命令集,写入临时文件。在此期间,主进程将重写期间新增的命令追加到 AOF 缓冲区和 AOF 重写缓冲区。子进程完成重写后,通知主进程,主进程将 AOF 重写缓冲区的内容追加到临时文件,用新文件替换旧的 AOF 文件。
关键特性:
- 非阻塞性:子进程负责重写,主进程仍然处理请求,仅最后同步阶段会短暂阻塞;
- 数据安全:若重写失败,旧的 AOF 仍可用,Redis 启动时优先加载 AOF 恢复数据;
- 混合持久化:重写后的 AOF 可包含 RDB 格式数据前缀,进一步压缩体积。
为什么要先执行 Redis 命令,再把数据写入 AOF 日志呢?
优点:
- 保证正确写入:如果当前命令的语法有问题,错误的命令记录到 AOF 日志之后可能还需要语法检查。先执行 Redis 命令,再把数据写入 AOF 日志可以保证写入日志的都是正确的命令;
- 不阻塞当前的写操作:写操作命令成功后再将命令写入到 AOF,可以避免阻塞写操作。
缺点:
- 数据可能会丢失:执行写操作命令和记录日志是两个过程,如果 Redis 还没来得及将命令写入到磁盘就发生了宕机,会有数据丢失的风险;
- 阻塞其它操作:虽然先执行 Redis 命令再写入 AOF 日志不会阻塞当前命令操作,但因为 AOF 日志的记录也是在主线程进行的,故当 Redis 把日志文件写入磁盘时,会阻塞后续操作。
AOF 的重写的具体过程?
【与我之前整理的 AOF 磁盘重写机制当中 AOF 的重写过程一致,此处复制一下】
首先,主进程 fork 一个子进程,子进程利用写时复制技术,避免阻塞主进程。之后,子进程遍历数据库,将当前数据状态转为等效的最小命令集,写入临时文件。在此期间,主进程将重写期间新增的命令追加到 AOF 缓冲区和 AOF 重写缓冲区。子进程完成重写后,通知主进程,主进程将 AOF 重写缓冲区的内容追加到临时文件,用新文件替换旧的 AOF 文件。
AOF 子进程的内存数据跟主进程的内存数据不一致怎么办?
Redis设置了一个 AOF 重写缓冲区,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到AOF 缓冲区和AOF 重写缓冲区。当子进程完成 AOF 重写工作后,会向主进程发送一条信号。主进程收到该信号后,会调用一个信号处理函数,将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,使得新旧两个 AOF 文件所保存的数据库状态一致;新的 AOF 的文件进行改名,覆盖现有的 AOF 文件。
不难看出,Redis 的 AOF 重写机制的三个关键点在于:
- 主进程 fork 一个子进程,子进程负责重读数据库并优化 AOF 指令;
- 在此期间,主进程可正常进行写操作,并同时将命令记录到两个缓冲区,分别是 AOF 缓冲区和 AOF 重写缓冲区;
- 子进程完成 AOF 重写后,通知主进程,将新的 AOF 日志与 AOF 重写缓冲区合并,构成新的 AOF,覆盖旧的 AOF。
RDB 在执行快照期间,数据可以修改吗?
RDB 执行快照期间,数据可以修改,这是通过 Redis 的写时复制(Copy-On-Write,COW)机制实现的。具体流程如下:
- 触发 RDB 快照时,Redis 主进程会 fork 一个子进程。子进程负责将内存数据写入磁盘的 RDB 文件,主进程则继续处理客户端的读写请求;
- 写时复制机制:子进程生成快照时,会复制父进程的内存页表(逻辑复制,而非物理拷贝)。如果父进程修改了某个内存页,OS 会将该内存页标记为脏页,此时触发 COW 机制,即复制原始内存页的副本,主进程修改副本,子进程继续读取原始内存页。
重点在于,快照保存的是 fork 时刻的数据状态,后续主进程执行写操作产生的修改不会影响快照当中的内容。因此,一定要理解,RDB 执行快照期间,仍然可以执行 Redis 写操作对数据进行修改,但是此时修改的不是快照当中的内容,而是真实的数据内容,快照生成期间执行的写命令与快照本身无关,因为快照记录的是 fork 时刻的数据状态。