redis_RDB与AOF持久化

6 min read

redis持久化

主要有RDB快照和AOF(append only file)两种形式。

RDB

生成快照,保存的是编码压缩后的二进制数据,体积较小,便于快速恢复,但是每个快照都是全量的数据,所以生成快照的时间较长。需要通过bgsave开辟子进程来进行快照的生成。缺点是单次生成较慢,因而需要定期生成,中间出现宕机则无法恢复部分数据。

bgsave过程中,父进程继续接受请求,这些写请求会缓存在内存写缓冲区中,并等bgsave操作完成之后,将这部分数据也同步到磁盘。

AOF

打印写操作的指令,保存的是写操作指令,保存时机是写入完成后,此时就可以不用检查指令。AOF因为每次只写一句指令,所以单次执行轻量,是主线程执行的,可以很好的保证实时性。

image

AOF过程中write写入文件的过程,其实写入的是内核缓冲区,内核会调度什么时候将缓冲区的数据flush或者叫sync到文件系统。但是内核不一定什么时候调度,所以为了能保证数据落盘,可以自行调用fsync,redis中有三种appendfsync策略:

  • always: 运行完write后同步运行fsync
  • everysec(默认): 每秒运行一次fsync,将这一秒的缓冲区刷到磁盘。
  • no: 等OS自己落盘

AOF rewrite

重写是指当AOF文件太大的时候,需要触发重写,来将已存在的指令进行简化压缩。

重写的过程不需要操作AOF文件,只需要扫描全表,生成对应的set语句即可。因为这个过程也是一种重型操作,所以redis使用单独的进程来执行。不阻塞主进程的继续服务,期间新的cmd,需要专门记录到重写缓冲区中,等子进程rewrite完成后,需要将缓冲区中的内容追加到rewrite的文件尾部。该过程是主线程操作的会产生阻塞。

Fork

上面两种方式在bgsaverewrite的时候,都提到了重型操作,需要用子进程来执行,子进程如何获取父进程的状态和数据的呢。答案就是fork这个系统调用,在当前进程调用fork,会创建一个内存结构与父进程完成一样的进程。这样就可以执行相应的操作了。

当然fork有一些OS级别的优化,创建的进程不一定就是和父进程内存同样大,其实子进程是拷贝了父进程的页表数据并标记为只读,即内存地址指向的数据是一样,当子进程需要修改(写)数据的时候,发现是只读的,就会先拷贝一份数据,然后再写,而不改变原物理内存的数据,即不影响父进程的运行。这也被称为copy-on-write。

当父进程修改一个内存页时,操作系统会检查该页是否被子进程共享。如果被共享,则操作系统会为子进程复制一份该页的副本,并将复制后的页设置为只读,然后将写时复制标记清除,使得子进程访问该页时从自己的地址空间中获取数据。这样,子进程就可以独立地访问该页,而不会受到父进程的影响。

如何选择RDB or AOF

如果对数据丢失不敏感,可以使用RDB作为持久化策略。

否则可以使用RDB+AOF的混合模式,混合模式即RDB定期生成,AOF存储的是RDB快照+快照后的指令,是一种混合格式的file。重写的时候就可以触发生成RDB快照,然后把快照放在AOF文件头部了,这样就克服了AOF加载较慢的缺点了。