Redis的持久化机制有两种,第一种是快照RDB
,第二种是AOF
日志。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。AOF 日志在长期的运行过程中会变的无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长。所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身。
快照原理
Redis客户端同时提供两个命令来生成 RDB 存储文件,也就是SAVE
和BGSAVE
,通过命令的名字我们就能猜出这两个命令的区别。其中 SAVE 命令在执行时会直接阻塞当前的线程,由于 Redis 是 单线程 的,所以 SAVE 命令会直接阻塞来自客户端的所有其他请求,这在很多时候对于需要提供较强可用性保证的 Redis 服务都是无法接受的。我们往往需要 BGSAVE 命令在后台生成 Redis 全部数据对应的 RDB 文件,当我们使用 BGSAVE 命令时,Redis 会立刻fork
出一个子进程(fork时会阻塞主进程,阻塞时间与数据量成正比),子进程会执行『将内存中的数据以 RDB 格式保存到磁盘中』这一过程,而Redis服务在BGSAVE
工作期间仍然可以处理来自客户端的请求。
使用fork
的目的是为了不阻塞主进程来提升 Redis 服务的可用性。fork
之后的子进程是可以共享父进程内存中的数据的,而fork
带来的额外性能开销相比阻塞主线程也是可以接受的。
COW
子进程做数据持久化,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。但是父进程不一样,它必须持续服务客户端请求,然后对内存数据结构进行不间断的修改。Redis借助OS提供的写时复制技术(Copy-On-Write)保证子进程读取的内存快照的完整性。
COW的原理是,子进程复制了主线程的页表,所以通过页表映射,能读到主线程的原始数据,而当有新数据写入或数据修改时,主线程会把新数据或修改后的数据写到一个新的物理内存地址上,并修改主线程自己的页表映射。所以,子进程读到的类似于原始数据的一个副本,而主线程也可以正常进行修改。
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长。如果Redis服务在执行RDB的过程中需要处理大量写请求,会造成内存占用急剧上升,当服务器内存仅为Redis数据量的2倍或者更少,如果服务器开启了swap,那么当Redis访问的数据在磁盘上时,性能会急剧下降,如果没有开启swap,有可能直接出发oom,被操作系统kill掉。
AOF原理
AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。
Redis 会在收到客户端修改指令后,进行参数校验进行逻辑处理后,如果没问题,就立即将该指令文本存储到 AOF 日志中,也就是先执行指令才将日志存盘。这点不同于leveldb、hbase等存储引擎,它们都是先存储日志再做逻辑处理。
AOF 重写
Redis 在长期运行的过程中,AOF 的日志会越变越长。如果实例宕机重启,重放整个 AOF 日志会非常耗时,导致长时间 Redis 无法对外提供服务。所以需要对 AOF 日志瘦身。
Redis 提供了bgrewriteaof
指令用于对 AOF 日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。
AOF 日志是以文件的形式存在的,当程序对 AOF 日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘的。存在缓存就意味着,如果未刷盘之前服务器掉电,就会产生数据丢失。
Linux提供了fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。但是fsync
是一个磁盘 IO 操作,很慢!所以在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync 操作,周期 1s 是可以配置的。
总结
通常Redis的master节点是不会进行持久化操作,因为RDB比较耗费系统资源而AOF的fsync
会阻塞Redis的服务,所以持久化操作主要在从节点进行。此外可以考虑Redis 4.0之后支持的混合持久化以减少Redis重启的时间。