考虑这样一个场景,当Redis发生异常重启时,Redis 的数据都是存在内存中的,这时该怎么找回来?
实际上,Redis 的持久化主要有两大机制实现 —— AOF日志 和 RDB快照。
AOF日志
AOF日志如何实现的
AOF日志是 Redis 执行命令后,把数据写入内存,然后记录下这条命令到AOF日志中。
先写数据,再写日志,这样可以避免额外的命令语法检查开销,并且不会阻塞当前的写操作。
但是这样,也会有潜在风险:
如果在写完数据后,还没来得及写日志,Redis 就宕机了,那么这条命令就没有记录到 AOF 日志中。
其次,虽然先写数据再记日志,不会阻塞当前的请求。但是写AOF日志,也是在主线程中执行的,并且是写入磁盘,依然会阻塞下一个请求。
AOF在何时写入磁盘
既然 AOF 在写入磁盘,会导致阻塞当前线程。那么,如何降低阻塞的风险呢?
实际上,AOF 的配置项 appendfsync 控制了 AOF 日志写回磁盘的时机,它有三个可选项:
- Always,同步写回。就是每个写命令执行完后,马上将日志写回磁盘。
- Everysec,每秒写回。每个写命令执行完后,把 AOF日志写入内存缓冲区,然后每秒把缓冲区内容写道磁盘一次。
- No,操作系统控制写回。每个写命令执行完后,把命令写到内存缓冲区,由操作系统决定何时写回磁盘。
以上三种AOF日志写回磁盘的时机,都各有利弊。如果写回操作频繁,可以降低 Redis 宕机后,数据丢失的风险,但是会导致 Redis 的线程阻塞,执行效率变低。相反,如果写回操作频率低,可以提高 Redis 的执行效率,但是一旦宕机,未来得及写入磁盘的 AOF 日志对应的写命令数据就会丢失。
实际上,Redis 的 AOF日志,通常都是根据实际业务场景决定何时写回磁盘的。 这就是一种性能和可靠性的取舍。
AOF 日志重写
虽然写命令的增加, AOF 日志会不断增大,当 AOF 日志大到一定程度时,就会导致性能降低的问题。甚至,可能导致 AOF 日志将磁盘占满了。另外,当 AOF 日志太大时,Redis 宕机后,根据 AOF 恢复数据,整个恢复过程会非常缓慢。
这时候, AOF重写机制就派上了用场。
AOF 日志记录的是写操作,每接受到一条写命令,就把这条命令追加写入 AOF 文件。 这时,如果一个 key,对应的 value 值被反复修改,其实,对应的有意义的数据,应该是最新的数据,过去的修改记录并不重要了。所以,AOF 重写机制,会根据这个 key 当前的最新状态,生成对应的写入命令,并写入 AOF重写文件。这样一来,这个 键值对 在 AOF 日志中只占用了一条命令。 并且,恢复数据时,只根据这条命令,就可以完成这个键值对的数据恢复。
在生成了 AOF重写文件后,用它替换当前的 AOF 文件,就完成了 AOF 文件的压缩。
现在知道了AOF的重写机制后,需要思考这样一个问题:AOF重写后,日志会变小,但是要把这个 Redis 的最新数据对应的操作日志都写回磁盘,依然会很耗时,这又怎么办呢?
实际上,与AOF日志正常写入流程不同,AOF重写不是由主线程完成的,而是由子进程 bgrewriteaof 完成的。
AOF 重写的过程可以总结为 “一个拷贝,两份日志”
一个拷贝:AOF重写,Redis 的主线 fork 出子进程 bgrewriteaof,将主线程的内存拷贝一份给子进程,然后 子进程 再把拷贝过来的数据,写成操作日志,记入 AOF 重写日志。
两份日志:主线程 fork 出子进程后,依旧可以正常处理新过来的请求,如果在此时,有新的写命令,这条写命令会计入到两个日志中。
在主线程中,会将这个写命令写入 AOF 缓冲区中,在执行 AOF 磁盘写回时,将此命令从 AOF 缓冲区中取出写入磁盘。 另外,这个操作也会被写入到 AOF 重写缓冲区,等子进程所有的拷贝数据都生成写命令并写入了 AOF 重写日志后,在 AOF 的重写缓冲区也会写入 AOF 重写文件。这样,就能保证在生成 AOF 重写文件过程中,新产生的 写命令被遗漏。
AOF日志总结:
1、AOF 是指每条写命令执行后,将此命令写入 AOF 文件,以实现 Redis 重启能通过执行 AOF 文件中的命令恢复数据。
2、写 AOF日志是在主线程中执行的,为了降低主线程阻塞情况,AOF 日志会写入 AOF缓冲区,有三种不同的模式控制何时将 AOF 缓冲区内容写回磁盘。
3、AOF 日志过大时,有 AOF 重写机制来压缩 AOF 日志。 AOF 重写是由 主线程 fork 的子进程完成的,不会阻塞当前主线程。并且,有 “一处拷贝,两处日志”的机制保证在 AOF 重写过程中,新产生的 写命令 不会被遗漏。 AOF重写完成后,由AOF重写日志替代当前 AOF 日志。
RDB 快照
上面提到的 AOF 日志实现持久化,是指当 Redis 宕机重启时,可以通过执行 AOF 日志中的命令实现数据恢复。 然而,如果 AOF 日志很大时,Redis 恢复数据就会很慢。 那有没有另外一种持久化的技术,能实现 Redis 的快速恢复数据呢?
RDB 快照就能实现快速恢复。 RDB 快照就是把某一时刻的 Redis 数据,以文件的形式写到磁盘中,实现数据持久化。 然后在 Redis 异常重启时,将 RDB 文件直接读入内存,就能实现数据恢复。
这种RDB快照数据恢复的方式,当然比根据 AOF日志 恢复数据要快。但是这里依然要考虑两个问题:
1、生成 RDB 文件,需要对 Redis 的全量数据进行备份,这会导致 Redis 主线程的阻塞吗?
2、何时生成 RDB 文件?
通过 bgsave 子进程,非阻塞式的生成 RDB 文件
我们可以根据 bgsave 命令,创建子进程,从而在不阻塞主线程的情况下生成 RDB 文件。
生成 RDB 文件时,主线程能执行写命令吗?
在生成 RDB 文件时,如果主线程进行了写操作,修改了正在执行快照的数据,导致了数据的不一致怎么办呢?
实际上,Redis 借助了操作系统提供的写时复制技术(Copy-On-Write),在执行快照时,如果 Redis 需要对某份数据进行写操作,会生成这个数据的副本,然后,主线程在这个副本上进行修改,bgsave子进程写入的依然是原来的数据。然后,在快照结束后,再将此副本覆盖原本的数据。
何时生成 RDB 文件
在考虑何时生成 RDB 文件时,很自然地考虑到,如果写 RDB 文件频率高,那么会降低 Redis 的执行效率(写磁盘会带来额外开销,并且 fork 子进程也会阻塞当前主线程)。但是如果写 RDB 文件频率低,会导致 Redis 宕机恢复时,某些数据的丢失。
这里,可以通过增量快照的方式,降低快照导致的额外开销。 增量快照,顾名思义,就是在做完第一次全量快照以后,后面再进行快照时,只对修改了的数据快进行快照记录即可。
但是,增量快照也会带来其他问题。Redis 需要记住,哪些数据进行了修改,并且需要额外的空间去记录这些被修改的数据。
RDB 快照结合 AOF 日志 实现Redis 数据恢复
AOF 日志恢复数据耗时长,而 RDB 快照不好控制快照频率。其实,可以通过两者结合的方式,来实现 Redis 的数据恢复。
在进行 RDB 全量快照以后,通过 AOF 日志记录后面进行的写操作。然后,在下一次 RDB 全量快照时,清空 AOF 日志,重新记录这次 RDB 快照后产生的写操作。
在恢复 Redis 的数据时,只需要读取最新的RDB日志到内存中,再根据执行 AOF 日志的命令,恢复快照后产生的数据修改,就能恢复完整的数据。
这样一来,RDB 快照的频率不需要太高,AOF 日志也不会记得特别长。