Redis 持久化

持久化有哪几种方式,各有什么问题。每种持久化的流程是什么样的,AOF重写是什么机制,解决了什么问题,fork底层函数,COW机制。

面试

在面试的时候如果被问到Redis持久化的时候,怎么回答比较好?我想从以下几个角度展开,一定会让面试官眼前一亮。

  • RDB冷备份以及AOF热备份
  • fork函数
  • COW(Copy on write)写时复制机制
  • AOF命令重写

COW机制

写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

上面一段话摘自维基百科,简单的理解就是在有多个进程请求同一个文件的时候,都会先持有这份文件,但是只有在程序试图去修改的时候,这个程序会先复制一份文件,后面的修改会基于复制的文件,不会改动源文件。这样其他的程序所持有的还是未改动的文件。

RBD

RBD是Redis持久化方式的一种,主要是基于COW机制实现的一种类似快照的持久化方式。在Redis运行中将所有的数据复制一份输出到磁盘上,其分为两种触发机制手动触发以及自动触发。

触发机制

1.手动触发

手动触发主要依靠开发人员使用save以及bgsave命令来启动,save和bgsave主要区别在于save是阻塞式的,bgsave会fork一个子进程来进行持久化,除了在fork过程中会出现一小段时间的阻塞外,其余时间不会阻塞服务,在持久化完成之后会自动结束进行。

2.自动触发

除了手动触发外,还有自动触发机制,自动触发场景如下:

  • 开启save配置(save m n),每m秒数据出现n次修改就会自动触发
  • 从节点全量复制
  • 执行debug reload重新加载redis
  • 在没有开启AOF持久化时,执行shutdown命令

bgsave流程

bgsave命令首先会判断是否存在RBD或者AOF子进程,如果没有则创建,若存在则直接返回。紧接着父进程会fork一个子进程,fork这段时间会出现短时间阻塞,然后父进程继续响应客户端,子进程生成RBD文件,完成持久化之后通知父进程。

bgsave命令流程说明,图片摘自《Redis开发与运维》一书

RBD文件

RDB的保存于dir配置指定下的目录,除此之外还可以使用config set dir动态修改RDB文件的位置以及config set dbfilename动态修改RBD文件的名称,这个在线上环境的磁盘出现问题或者写满的时候有用。

cow的运用

fork出来的子进程需要对父进程的数据进程持久化,但是这个时候父进程会响应来自客户端的请求,对数据进行修改,如果此时出现了一个非常大的数据正在持久化,但是突然客户端来了一个删除命令,这个时候持久化就会失败。但是如果基于cow机制的话就可以避免这个问题,父进程修改的是复制出来的数据,子进程持久化的还是原来的数据没有被修改,不会受父进程的影响。

RDB 优缺点

优点:

  • RDB适合用来做冷备份,加载速度比AOF快

缺点:

  • RDB文件的版本有很多,版本之间互相不兼容
  • 不适合实时备份,fork操作会很耗性能,与redis的高性能相违背

AOF

相对于RDB来说,AOF是可以进行实时(秒级)持久化的。比较适合用来做热备份,通过配置文件中的appendonly yes来开启,appendfilename为AOF持久化的文件名,默认为appendonly.aof。

AOF工作流程

AOF持久化首先会将客户端请求的命令放入到AOF缓存中,然后根据磁盘的刷新策略写入到磁盘中,当AOF文件达到上限时,会进行AOF重写,对文件进行瘦身,最后在redis重启的时候重新加载下AOF文件即可。

命令写入过程

服务端接收到客户端按照RESP协议文本格式的命令后,会将其直接写入到aof缓存区内。使用aof缓存可以提高redis的性能,以及使用不同缓存区刷新策略,可以在性能和安全方面做出平衡。
AOF刷新策略

  • always 命令写入缓冲区后,调用fsync写入到磁盘上。即没接收到一条命令就写入到磁盘上。
  • everysec 命令写入缓冲区后,同步文件操作有专门线程每秒钟同步一次。(推荐使用)
  • no 同步操作由硬盘操作,同步周期最长为30s

重写机制

AOF文件随着命令不断被写入体积会越来越大,但是已经同步过的命令并不是所有的命令都是有效的,有的命令是可以被优化掉,这样可以减少AOF文件体积,还可以降低加载时间。

优化场景

  • 内存中已经过期的数据不会被同步。
  • 旧的AOF文件中无效的命令,del、hdel、srem等。
  • 命令合并,将多条命令合并成一条指令。

AOF重写触发机制

手动触发:bgrewriteaof
自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定,简单的理解就是当前文件大小大于重写最小值并且aof文件增长率大于设置的值。

auto-aof-rewrite-min-size表示运行AOF重写时文件最小体积,默认为64MB,auto-aof-rewrite-percentage代表当前AOF文件空间(aof_current_size)和上一次重写后的文件空间占比。自动触发时机=aof_current_size>auto-aof-rewrite-min-size && (aof_current_size - aof_base_size) / aof_base_size >= auto-aof-rewrite-percentage

AOF重写流程

AOFfork的子进程只是用来重写已经存在AOF文件,AOF写入还是由父进程来完成。

1.执行AOF重写请求
如果当前已经存在AOF重写任务,则当次请求不执行,如果已经有bgsave正在执行,则当前请求会等待该任务执行完成后启动。
2.父进程fork一个子进程用来重写AOF文件,将客户端的命令同时保存到aof_buf以及aof_rewrite_buf中

  • 3.1 原有的AOF持久化操作会正常进行,保持从aof_buf中读取数据写入到旧的aof文件中。
  • 3.2 同时会将命令写入到aof_rewrite_buf中,保证命令重写操作后的数据一致性。

4.子进程的重写操作会根据内存快照进行重写,生成新的aof文件。
5.1子进程完成重写之后会发给父进程发送一个完成信号,父进程会更新相关的统计数据

  • 5.2父进程把aof_rewrite_buf缓冲区的数据重写到新的aof文件中
  • 5.3使用新的AOF文件替换老的AOF文件

体会:在AOF重写过程中会有aof_buf以及aof_rewrite_buf两个缓存区,第一个aof_buf缓存区主要的目的就是用来保持原有的AOF持久化的正常进行。第二个缓冲区aof_rewrite_buf缓冲区是保存在重写期间接收到的命令,一开始我在思考的时候不是很明白,aof_buf也会保存重写期间的数据,为什么还要在保存一份到rewrite的缓存区中?主要就是aof_buf是提供给持久化操作的,在重写操作完成之后是要将旧的文件替换掉的,所有这个时候需要将重写期间的命令单独保存下来,等重写完成之后再同步rewrite的数据,从而保证了AOF的连续性。

总结

这篇文章主要用来概括性记录下redis持久化操作所包含的知识点,大部分内容是我整合起来的。但是基本上都是加以自己的理解写出来的,没有直接CV,当然图片是我直接抄过来的。主要目的就是大概的记录下AOF涉及到的知识点,方便后期深入了解,以及回顾的时候可以有个快速的了解。

参考文献

《Redis开发与运维》