一、为什么要持久化

        众所周知,redis是内存数据库,即所有数据都存储在内存,当服务器关闭时所有数据都将丢失。而持久化则是为了将redis中的数据以某种格式异步保存到硬盘中,在redis重启时仍然可以获取到以前的数据。另外持久化也被称为最简单的高可用方式,可以实现数据备份。

        Redis提供了两种持久化方式:RDB(快照)和AOF(日志)

二、RDB持久化

1、含义

RDB持久化指redis在运行期间按指定好的时间间隔生成当前redis数据的时间点快照,即保存当前时间点的数据集,并将该数据集保存在一个默认名为dump.rdb(可通过配置修改)的的二进制文件中,在下次redis启动时将该文件的数据载入到redis数据库(内存),从而实现数据的持久化(保存)。

2、RDB持久化触发方式

(1)手动触发rdb持久化的方式有两种,一种是在客户端执行 save(同步) 命令,一种是执行 bgsave(异步) 命令。
save:客户端输入save命令,服务端接收到命令后开始创建rdb文件。执行save命令会阻塞redis服务器主进程,即save命令执行过程中直到rdb文件彻底生成前服务器无法客户端的任何请求,这是一个很粗暴的方式所以不会使用,时间复杂度O(n)。
bgsave:执行bgsave命令时redis会调用fork()函数创建一个子进程,由这个子进程来生成rdb文件,而主进程则继续处理客户端的请求,当rdb文件创建成功后子进程会返回结果给主进程,时间复杂度O(n)。在该过程中,只有fork子进程时会阻塞服务器(一般很快),同时fork的时候会消耗内存,这是其缺点。

(2)自动触发

在 redis.conf 配置文件中有如下默认配置

save 900 1
save 300 10
save 60 10000

save 900 1:表示在900秒内redis中的数据有1次以上修改则执行一次bgsave命令将当前数据保存为rdb文件。其他两条save配置也是这个意思,只要这三个配置满足其中一个,则redis就执行bgsave命令(即save m n)。

save m n 原理:

这个配置其实是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。

redis服务器的周期性操作函数(serverCron)默认会每隔100ms执行一次,该函数主要用于包括检查 save m n 配置的条件是否满足等对服务器的状态进行维护的工作。dirty计数器主要是为了记录redis上一次执行bgsave/save后服务器数据做了多少次修改,每次执行完bgsave/save都将置0。注意dirty记录的是服务器进行了多少次修改,即lpush list a b 一条命令算执行两次修改。lastsave时间戳主要用于记录上一次成功执行save/bgsave的时间。

所以 save m n 命令执行需要满足两个条件: 当前时间-lastsave > m 、 dirty >= n

(3)其他场景下触发(不容忽略的触发机制)

  • 在主从复制场景下,如果从节点执行全量复制,则主节点会执行bgsave命令,并将rdb文件发送给从节点;
  • 执行shutdown命令时,自动执行rdb持久化(bgsave)。
  • debug reload时

3、RDB持久化优点:

  • 生成的rdb文件较紧凑,即体积较小,网络传输快,适合全量复制。
  • 数据量大时较aof持久化方式的恢复速度快。
  • 可以通过配置控制rdb文件生成的时间。
  • 与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。
  • 在自动触发时,redis都会执行fork()函数生成子进程来生成rdb文件,而不占用主线程。

4、RDB持久化缺点:

  • 数据量很大时耗性能,上面的fork()子进程比较耗时(消耗内存),导致服务器出现阻塞现象。
  • 跟aof比,不是非常耐久。
  • rdb持久化过程较繁琐,一般在较长一段时间才保存一次rdb文件。也就是说,在这个时间段内如果服务器宕机,则这个时间段的数据将丢失。

5、执行过程

在进行rdb持久化时,服务器执行以下操作:

  • redis调用 fork()函数创建了子进程。
  • 子进程创建后开始将redis中原有的数据集写入到一个临时 RDB 文件中。
  • 当子进程完成对新 RDB 文件的写入时,会用新 rdb 文件替换旧的 rdb 文件并将其删除。

6、配置

redis.conf 配置文件中关于rdp持久化配置说明如下:

  • save m n:bgsave自动触发的条件,当删除所有save m n配置则不会自动执行bgsave(最佳配置会删去)。
  • dir ./data/rdb:rdb文件生成的目录位置
  • dbfilename dump-6379.rdb:rdp文件名称
  • rdbcompression yes:是否开启rdb文件压缩
  • rdbchecksum yes:是否开启rdb文件的校验,即在写、读(启动载入)文件时检查数据是否损坏;关闭会带来性能优化但无法发现文件是否损坏。
  • stop-writes-on-bgsave-error yes:在执行bgsave出错时,redis是否停止写命令;设置为yes,当硬盘出现问题时可以及时发现,避免数据的大量丢失;设置为no,则不管bgsave的错误继续执行写命令。

三、AOF持久化

1、含义

AOF持久化指redis在运行期间,根据配置所选策略,将所有执行的命令写到一个AOF文件中。当redis服务器宕机,下次启动是可载入该AOF文件执行之前的命令从而恢复数据,redis默认该持久化方式是关闭的。

2、三种策略

AOF开启的时候,redis在执行写操作的时候同时会把写命令刷新到缓冲区(aof_buf),再由不同的策略决定什么时候将缓冲区的数据写到硬盘的AOF文件中。AOF提供了三种持久化策略:alwayseverysecno

always:每条写命令执行的时候都实时fsync到硬盘AOF文件

everysec:每秒把缓冲区fsync到硬盘AOF文件

no:由操作系统决定什么时候fsync到硬盘AOF文件,redis不会主动调用fsync去将AOF日志内容同步到磁盘,完全依赖于操作系统的调试,对大多数Linux操作系统,是每30秒进行一次fsync。

 

always

everysec

no

优点

不丢失数据

每秒一次fsync,只丢1秒数据

不用管

缺点

IO开销较大,不断进行IO操作

会丢1秒数据(实际可能2秒)

不可控

3、AOF重写

(1)定义:AOF持久化时,会将所有写命令写到AOF文件中,当操作比较频繁时文件会一下子变很大。同时用户如果不断执行多条自增或者对同一个key做多次set操作,将会导致AOF存在很多无意义的写命令,这对文件大小以及数据恢复都很不友好。因此AOF持久化时会有一个“AOF重写“的操作,当文件达到某个条件的时候会执行重写(bgrewriteaof),将文件中多条命令精简为最少的命令从而减少硬盘占用量、加快恢复速度。

(2)两种触发方式:手动执行bgrewriteaof命令、根据aof重写配置自动触发

bgrewriteaof:客户端发起bgrewriteaof命令,服务端收到命令后会fork一个子进程,再由子进程对AOF文件进行重写

配置方式:redis.conf配置文件中有 auto-aof-rewrite-min-size(aof文件重写需要达到的大小)、auto-aof-rewrite-percentage(文件增长率,即目前的AOF文件的大小超过上一次重写时的AOF文件的百分之多少时再次进行重写,如果之前没有重写过,则以启动时AOF文件大小为依据) 这两个配置,重写自动触发机制满足如下两个条件:aof_current_aof >auto-aof-rewrite-min-size  ;  (aof_current_aof -aof_base_size )/aof_base_size >auto-aof-rewrite-percentage。其中 aof_current_aof 表示AOF当前文件大小(字节)  、 aof_base_size 表示上次启动和重写后AOF文件的大小(字节)。

(3)重写执行步骤

  • redis主进程调用fork()函数,创建子进程。
  • 子进程开始根据旧的AOF文件将新 AOF 文件的内容写入到临时文件。
  • 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾。这样就算重写途中宕机,旧的AOF文件还是完整的。
  • 当子进程完成重写工作时会给主进程发一个返回,主进程则开始将内存缓存的所有数据追加到新AOF文件末尾,之后就只对新AOF文件尽心追加操作。

(4)重写能够压缩AOF文件原因:

  • 过期的数据不再写入文件
  • 无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd myset v1, del myset)等等
  • 多条命令可以合并为一个

4、AOF的执行流程如下:

  • 命令追加(append):将redis的写命令追加到缓冲区aof_buf,不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为redis负载的瓶颈。
  • 文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘;
  • 文件重写(rewrite):定期重写AOF文件,达到压缩的目的。

5、AOF持久化优点

  • 可选不同的 fsync 策略,默认策略为每秒钟 fsync 一次,如果服务器挂了最多只丢失一秒钟的数据。
  • AOF 文件是一个只进行追加操作的日志文件,操作较简单因此压力较小。
  • 可用 redis-check-aof 工具修复有问题的aof文件。
  • 可重写压缩文件。
  • 相对于RDB持久化,其数据集更完整。
  • 完全耐久。

6、AOF持久化缺点

  • 相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB
  • 恢复速度慢、对性能影响大。

7、配置

redis.conf 配置文件中关于aof持久化配置说明如下:

  • appendonly yes  :是否开启AOF,设置为yes开启aof持久化(默认关闭)
  • appendfilename "appendonly-6379.aof":AOF文件名
  • appendfsync everysec:fsync持久化策略
  • dir ./ :AOF文件所在目录
  • no-appendfsync-on-rewrite yes:AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载,但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
  • auto-aof-rewrite-percentage 100:文件重写需要满足的增长率
  • auto-aof-rewrite-min-size 64mb:文件重写需要满足的大小
  • aof-load-truncated yes:如果AOF文件结尾损坏,redis启动时是否仍载入AOF文件

四、两种持久化方式对比及总结

当两种持久化一起使用,在redis启动时会先使用aof还原数据。当AOF开启,但AOF文件不存在时,即使RDB文件存在也不会加载。选择哪一种持久化需要结合实际场景,如果能接受几分钟数据丢失就使用rdb。一般而言rdb最佳策略:单机时关、主从时,主关从开。而aof最佳策略:开启、选择everysec、同时集中管理AOF重写。

这两种持久化的对比如下表:

 

RDB

AOF

启动优先级



体积



恢复速度



数据安全性

丢数据

根据策略决定

轻重



五、其他

(1)当服务器运行时需要修改配置,如开启aof,可执行如下操作:

config get appendonly   //得到关闭
config set appendonly yes
config rewrite

(2)fork

在两种持久化时都会涉及到fork操作,并且只有在fork时会阻塞主进程,所以这里也了解一下。

fork操作是同步操作 ,且其操作与内存量有很大关系,内存越大耗时越长。

改善(网上看到的):

  • 优先使用物理机或高效支持fork操作的虚拟化技术
  • 控制reids实例最大可用内存
  • 合理配置linux内存分配策略
  • 降低fork频率:例如放宽AOF重写自动触发时机,不必要的全量复制

(3)对于主从环境下的持久化设置

  • master节点:完全关闭持久化(包括RDB和AOF),这样可以让master的性能达到最好
  • slave节点:关闭RDB,开启AOF(如果对数据安全要求不高,开启RDB关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。

(4)aof持久化:当持久化策略选择everysec时,主进程在执行完写操作后调用系统write操作将命令写到aof_buf则返回,而命令从aof_buf到硬盘中的aof文件由专门的文件同步线程每秒调用一次,跟主进程无关。这样就会导致当硬盘负载过高,那么fsync操作可能会超过1s,此时redis主线程持续高速向aof_buf写入命令,但是数据一直卡着没fsync到硬盘,此时如果redis宕机则丢失的数据将远不止1秒。

redis的处理策略:主线程每次将数据扔给aof_buf返回后,会对比上次fsync成功的时间;如果距上次不到2s,主线程直接返回;如果超过2s,则主线程阻塞直到fsync同步完成(不再进行写操作)。因此,如果系统硬盘负载过大导致fsync速度太慢,会导致redis主线程的阻塞,此时使用everysec配置最多可能丢失2s的数据而非1s。