Redis是内存数据库,但一旦服务器宕机,内存中的数据将全部丢失。作为缓存,虽然可以从慢速数据库重新读取数据,但是也会增加慢速数据库压力。
所以选择数据持久化方式,避免从后端数据库中进行恢复
3 种持久化方式
- AOF:只追加文件(Append-Only File)
- RDB:快照(snapshotting)
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
AOF
好处
- 写后日志。先执行命令,把数据写入内存,然后才记录日志。对比写前日志(Write Ahead Log, WAL),写后日志通过先执行命令避免出现记录错误命令的情况。
- 不会阻塞当前的写操作。
风险
- 刚执行完一个命令,还没有来得及记日志就宕机。无法通过日志进行恢复
- 避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。
AOF三种日志写回策略
解决AOP风险的关键是控制一个写命令执行完后 AOF 日志写回磁盘的时机。在避免主线程阻塞和减少数据丢失寻找一个平衡。
appendfsync配置项
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- 基本不丢数据,但会影响主线程性能
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- 最多丢失上1秒内的数据,主线程性能得到较好释放
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
- 主线程性能很高,但回写不由Redis控制,较可能数据丢失
如何选择
根据系统对高性能和高可靠性的要求,来选择使用哪种写回策略
- 想要获得高性能,就选择 No 策略
- 如果想要得到高可靠性保证,就选择 Always 策略
- 如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略
日志文件过大问题
日志文件过大带来性能问题
- 文件系统本身对文件大小有限制,无法保存过大的文件
- 文件太大,之后再往里面追加命令记录的话,效率也会变低
- 发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到 Redis 的正常使用
AOF 重写机制
- 在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件。读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。
- 具有“多变一”功能,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令。
一个拷贝,两处日志
虽然 AOF 重写后,日志文件会缩小,但是,要把整个数据库的最新数据的操作日志都写回磁盘,仍然是一个非常耗时的过程。
和 AOF 日志由主线程写回不同,重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降
- 一个拷贝:每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
- 第一处日志:因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。
- 第二处日志:指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。
AOF非阻塞的重写过程
- 每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用额外的线程进行数据重写,所以,这个过程并不会阻塞主线程。
AOF小结
- AOF通过逐一记录操作命令,在恢复时再逐一执行命令的方式,保证了数据的可靠性。
- 考虑到对 Redis 性能的影响,它提供了 AOF 日志的三种写回策略,分别是 Always、Everysec 和 No,这三种策略在可靠性上是从高到低,而在性能上则是从低到高。三种写回策略体现了系统设计中的一个重要原则 ,即 trade-off,或者称为“取舍”,指的就是在性能和可靠性保证之间做取舍。
- 避免日志文件过大,Redis 还提供了 AOF 重写机制,直接根据数据库里数据的最新状态,生成这些数据的插入命令,作为新日志。就是一个拷贝,两处日志。
- 落盘时机和重写机制都是在“记日志”这一过程中发挥作用。落盘时机的选择可以避免记日志时阻塞主线程,重写可以避免日志文件过大。
- AOF的问题在于当使用AOF日志进行故障恢复时,Redis 的单线程设计使得命令操作只能一条一条按顺序执行,这个“重放”的过程就会很慢。
RDB
- 内存快照,就是把某一时刻的状态以文件的形式写到磁盘上,即使宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。这个快照文件就称为 RDB 文件,其中,RDB 就是 Redis DataBase 的缩写。
两个问题
- 如何取景?也就是说,我们打算把哪些人、哪些物拍到照片中;对应问题是对哪些数据做快照,这关系到快照的执行效率问题;
- 在按快门前,要记着提醒朋友不要乱动,否则拍出来的照片就模糊了。对应问题是做快照时,数据还能被增删改吗?这关系到 Redis 是否被阻塞,能否同时正常处理请求。
给哪些内存数据做快照?
数据越多,RDB 文件就越大,往磁盘上写数据的时间开销就越大。RDB 文件的生成是否会阻塞主线程,这就关系到是否会降低 Redis 的性能。
两个命令来生成 RDB 文件
- save:在主线程中执行,会导致阻塞;
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
快照时数据能修改吗?
- 避免阻塞和正常处理写操作并不是一回事。为了快照而暂停写操作,肯定是不能接受的
- COW(Copy-On-Write):写时复制技术。在执行快照的同时,正常处理写操作
Copy-On-Write
- bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据
- bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本(键值对 C’)。然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据(键值对 C)写入 RDB 文件
可以每秒做一次快照吗?
快照间隔时间可以决定宕机时丢失数据的多少。
一方面,频繁的执行全量快照,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
另一方面,bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了(所以,在 Redis 中如果有一个 bgsave 在运行,就不会再启动第二个 bgsave 子进程)。
通过增量快照来解决问题
增量快照
- 做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
混合使用 AOF 日志和RDB内存快照
- 内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
AOF 和 RDB 的选择
- 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择;
- 如果允许分钟级别的数据丢失,可以只使用 RDB;
- 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
总结
- AOF日志通过写后日志,带来的好处是尽可能的纪录操作,减少数据丢失。带来的问题就是AOF日志文件过大,对主线程造成压力和损耗
- AOF重写日志通过合并命令,解决了AOF日志文件过大的问题。带来的问题就是当故障恢复时,因为Redis的单线程处理,重放的过程较慢
- RDB快照通过生成RDB文件直接读入内存,解决了AOF日志文件重放慢的问题。带来的问题就是RDB文件的生成频率太慢会丢失数据,生成频率太快给磁盘带来很大压力,同时fork出bgsave子进程也会对主线程造成阻塞。
- 增量快照通过只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。借助AOF日志来记录增量。
- 混合使用 AOF 日志和RDB内存快照,既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势