跳转至

磁盘持久化

前言

本文主要介绍redis中的持久化相关技术,redis的持久化技术用于避免内存数据丢失

AOF

AOF(Append Only File)日志是Redis数据持久化的方式之一,它属于写后日志(命令执行完再记录日志,和数据库中的写前日志(WAL)刚好相反)

写后日志的好处

  1. 天然的过滤错误命令(只有正常执行的命令才会走到日志存储这里)
  2. 不阻塞写操作(因为日志是命令执行后才执行)

AOF存在的两个风险:

  1. 刚执行完写命令,还未记录日志(写盘),系统宕机了
  2. AOF在主线程中执行,如果写盘太慢,会阻塞后面的操作也无法执行

仔细观察,会发现这两个风险都与写盘操作(时机)有关,如果能控制写命令执行完后的AOF写盘时机,就可以解除这两个风险,AOF提供了三种写盘策略:

  • Always,同步写回:每个写命令执行完,立马同步日志到磁盘
  • Everysec,每秒写回:写命令执行完,先把日志写入到AOF内存缓冲区,每隔一秒将缓冲区数据写入磁盘
  • No,操作系统控制的写回:写命令执行完,先把日志写入到AOF内存缓冲区,由操作系统决定何时写入磁盘

三种策略的优缺点:

配置项 写回时机 优点 缺点
Always 同步写回 可靠性高,数据基本不丢失 每个写命令都要落盘,性能影响较大
Everysec 每秒写回 性能适中 宕机时丢失1s内数据
No 操作系统控制写回 性能好 宕机时丢失数据较多

由于AOF日志会记录所有的写命令,所以AOF文件会越来越大,主要有以下几个性能问题:

  1. 系统对文件大小有限制,无法保存过大文件
  2. 文件太大,每次追加内容时效率也低
  3. 若发生宕机,需要使用AOF文件恢复数据,日志文件太大,恢复数据较慢且需要一个一个执行写命令

重写机制

鉴于以上种种问题(基本是由于文件过大导致的),AOF提供了重写机制

重写机制具有"多变一"功能,也就是说,旧日志文件中的多条写命令,在重写后的新日志中变成一条命令,在使用AOF恢复数据时,只需要执行这一条命令也可以正确的恢复数据

AOF重写日志会阻塞吗?

不会,与 AOF 日志由主线程写回不同,重写过程由后台子进程bgrewriteaof来完成,也是为了避免主线程阻塞,导致数据库性能下降

AOF重写的过程可以总结为:"一个拷贝,两处日志"

一个拷贝是指:在进行重写时,主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof,里面就包含了数据库的最新数据,然后bgrewriteaof在不影响主线程的情况下,实现日志的重写操作,并生成新的日志重写文件

两处日志:

  • 第一处:主线程未被阻塞,仍然可以执行写操作,仍然会被记录AOF日志(这里Redis会将日志写入缓冲区)
  • 第二处:新的重写日志,主线程目前执行的写操作,也会被copy一份到重写日志的缓冲区,当完成日志重写后,重写日志会将缓冲区中的内容写入到重写日志中,以保证数据库最新状态的记录,此时可以使用重写日志文件替换旧日志文件了

RDB

前面了解到AOF是以日志文件的形式进行持久化,日志文件太大会影响数据恢复的时间(因为需要把日志中的命令都执行一遍),下面介绍另外一种持久化方案:内存快照(Redis Database,RDB)

与AOF相比,RDB记录的是某一时刻的数据,并不是操作,所以在数据恢复时可以直接把RDB文件读入内存,恢复相对比AOF的写命令快

内存快照需要考虑的两个点:

  • 对哪些数据进行快照?这关系到快照的执行效率
  • 做快照时,数据还能被增删改吗?(Redis是否被阻塞,能否同时正常的处理请求?)

针对第一个点:RDB执行的是全量复制,将内存中所有的数据都记录到磁盘中

第二个问题:执行快照时,是否会阻塞主进程(或者说是否影响redis的增删改?)

redis提供了两种命令来生成RDB文件,分别是save和bgsave

  • save:在主线程中执行,会导致阻塞(影响写操作)
  • bgsave:创建一个子进程,专门用于写RDB文件,避免了主线程的阻塞(Redis RDB文件生成的默认配置)

执行快照操作时,还可以对redis进行写操作吗?因为此时的数据正在写入RDB文件中

这个问题很重要,其实,在执行RDB时,主线程虽然并未阻塞,但是为了数据完整性,它只能对外提供读操作,而不能提供写操作,因为不能修改正在执行快照(RDB)的数据。

写时复制技术

为了快照而暂停写操作,肯定是不能被接受的,此时,redis会借助操作系统提供的写时复制技术(Copy-On-Write,COW),在执行快照的同时,正常处理写操作。

简单来说,bgsave子进程由主线程fork生成的,可以共享主线程的所有内存数据。bgsave子进程运行后,开始读取主线程的内存数据,并把它们写入RDB文件。此时如果发生读操作,那么主线程和bgsave子进程相互不影响,但是,如果主线程要修改一块数据,那么这块数据就会被复制一份,生成该数据的副本,然后主线程在这个数据副本上进行修改。同时,bgsave子进程可以继续把原来的数据写入RDB文件。

既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

快照的频率

多久做一次快照,快照间隔短,则丢失的数据相对较少,但是间隔短意味着频率高,就会导致磁盘IO的频率高,进而影响到性能。

主进程fork生成bgsave子进程时,会阻塞主线程一小会,频繁的进行快照会导致频繁的fork出bgsave子进程,进而导致主线程频繁产生阻塞(所以在redis中如果有一个bgsave在运行,就不会再启动第二个bgsave子进程)

那么,是否有其他较好的方案呢?

混合持久化

混合持久化是Redis4.0新引入的持久化策略,结合了RDB的快速恢复和AOF的数据完整性的优点,它首先以RDB格式保存当前数据状态,然后继续以AOF格式记录新的写操作,确保数据完整性并优化恢复速度

混合持久化的 RDB 内容只会在 AOF 重写 时被写入 AOF 文件中,因为该技术的目的之一也是改进AOF重写后文件仍然较大且恢复较慢的特点。

要启动混合持久化,需要AOF和RDB同时开启的前提下,在通过以下选项开启:

aof-use-rdb-preamble yes #yes表示开启

混合持久化的AOF重写和普通的AOF重写的区别:

在不使用混合持久化的情况下,普通的AOF重写是通过读取当前的内存数据并记录达到这一状态所需的最少命令来减少AOF文件的大小。

而混合持久化的AOF重写时,会首先将当前数据集以RDB格式快照的形式写入新AOF文件的首部,然后再追加新的写命令到文件末尾。