RDBRDB 持久化是把当前进程数据生成快照保存到硬盘的过程。 触发 RDB 持久化过程分为手动触发和自动触发。RDB 完成后会自动生成一个文件,保存在 dir 配置的指定目录下,文件名是 dbfileName 指定。
RDB 文件是经过压缩的二进制文件,存储路径既可以在启动前配置,也可以通过命令动态设定。动态设定存储路径在磁盘损害或空间不足时非常有用,命令为:config set dir {newdir} 和 config set dbfilename {newFileName}。
Redis 默认会采用 LZF 算法对生成的 RDB 文件做压缩处理,压缩后的文件远远小于内存大小,默认开启。
RDB 文件在 Redis 启动时自动载入,没有专门的命令。但是由于 AOF 的优先级更高,因此当 AOF 开启时,Redis 会优先载入 AOF 文件来恢复数据。只有当 AOF 关闭时,才会检测 RDB 文件,并自动载入。服务器载入 RDB 文件期间处于阻塞状态,直到载入完成为止。同时,在载入 RDB 文件的过程中会对 RDB 文件进行校验,如果文件损坏,则日志中会打印错误,Redis 启动失败。
RDB 的优点:
- RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每 6 小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中,用于灾难恢复。
- Redis 加载 RDB 恢复数据远远快于 AOF 的方式。
RDB 的缺点:
- RDB 方式数据没办法做到实时持久化/秒级持久化。因为 bgsave 每次运行都要执行 fork 操作创建子进程,属于重量级操作,频繁执行成本过高。
- RDB 文件使用特定二进制格式保存, Redis 版本演进过程中有多个格式的 RDB 版本, 存在老版本 Redis 服务无法兼容新版 RDB 格式的问题。
触发 RDB 持久化
手动触发:命令有 save 和 bgsave
- save:该命令会阻塞 Redis 服务器,直到 RDB 的过程完成,已经被废弃,因此线上不建议使用。
- bgsave:每次进行 RDB 进程都会 fork 一个子进程,由子进程完成 RDB 的操作,因此阻塞只会发生在 fork 阶段,一般时间很短。
NOTE:因为 save 命令的整个过程都会阻塞服务器,因此已经被废弃,线上环境要杜绝 save 命令的使用。
自动触发的场景:
- 根据配置项 save m n 自动触发,指定当 m 秒内发生 n 次变化时,会触发 bgsave。
- 在主从复制场景下,如果 SLAVE 执行全量复制操作,则 MASTER 会执行 bgsave 命令,并将 RDB 文件发送给 SLAVE。
- 执行 debug reload 命令重新加载 Redis 时, 也会自动触发 bgsave 操作。
- 默认情况下执行 shutdown 命令时, 如果没有开启 AOF 持久化功能则自动执行 bgsave。
RDB 执行流程
- 执行 bgsave 命令后,会先判断是否存在 AOF 或者 RDB 的子进程,如果存在,直接返回。
- 父进程 fork 操作创建一个子进程,fork 操作中父进程会被阻塞。
- fork 完成后,子进程开始根据父进程的内存生成临时快照文件,完成后对原有的 RDB 文件进行替换。执行 lastsave 命令可以查看最近一次的 RDB 时间。
- 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换。
- 子进程完成后发送信号给父进程,父进程更新统计信息。
RDB 常用配置
- save m n:bgsave 自动触发的条件。如果没有 save m n 配置,相当于自动的 RDB 持久化关闭,不过此时仍可以通过其他方式触发。
- stop-writes-on-bgsave-error yes:当 bgsave 出现错误时,Redis 是否停止执行写命令;
- 设置为 yes,则当硬盘出现问题时,可以及时发现,避免数据的大量丢失;
- 设置为 no,则 Redis 无视 bgsave 的错误继续执行写命令,当对 Redis 服务器的操作系统(尤其是硬盘)使用了监控时,该选项考虑设置为 no。
- rdbcompression yes:是否开启 RDB 文件压缩。
- rdbchecksum yes:是否开启 RDB 文件的校验,在写入文件和读取文件时都起作用。关闭 checksum 在写入文件和启动文件时大约能带来 10% 的性能提升,但是数据损坏时无法发现。
- dbfilename dump.rdb:RDB 文件名。
- dir ./:RDB 文件和 AOF 文件所在目录。
AOFAOF(Append Only File)持久化,以独立日志的方式记录每次写命令,即:每次写命令都会被记录到单独的日志文件中,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性, 目前已经是 Redis 持久化的主流方式。
Redis 服务器默认开启 RDB,关闭 AOF。要开启 AOF,需要在配置文件中配置:appendonly yes。AOF 文件名通过 appendfilename 配置设置, 默认文件名是 appendonly.aof。保存路径同 RDB 持久化方式一致,通过 dir 配置指定。
AOF 执行流程
由于 AOF 会记录 Redis 的每条写命令,因此 AOF 不需要设置触发条件。
与载入 RDB 文件类似,Redis 启动时载入 AOF 文件,也会进行校验,如果文件损坏,则日志中会打印错误,Redis 启动失败。但如果是 AOF 文件结尾不完整,例如:机器突然宕机等容易导致文件尾部不完整,且 aof-load-truncated(默认是开启的)参数开启,则日志中会输出警告,Redis 忽略掉 AOF 文件的尾部,启动成功。
注意:因为 Redis 的命令只能在客户端上下文中执行,而载入 AO F文件时命令是直接从文件中读取的,并不是由客户端发送。因此 Redis 服务器在载入 AOF 文件之前,会先创建一个没有网络连接的客户端(伪客户端),之后用它来执行 AOF 文件中的命令,命令执行的效果与带网络连接的客户端完全一样。
AOF 整体的执行流程分为 4 个步骤:
- 命令写入(追加,Append):将 Redis 的写命令追加到缓冲区 aof_buf。
- 文件同步(Sync):根据不同的同步策略将 aof_buf 中的内容同步到硬盘;
- 文件重写(Rewrite):定期重写 AOF 文件,达到压缩的目的。
- 重启加载
命令写入
AOF 命令写入,又称命令追加,内容直接是文本协议格式。例如 set hello world 这条命令, 在 AOF 缓冲区会追加如下文本:
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n 需要注意的是,Redis 先将写命令追加到缓冲区,而不是直接写入文件系统,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘 IO 成为 Redis 负载的瓶颈。这是大多数高性能数据库的常规设计。
Redis 使用单线程响应命令,如果每次写 AOF 文件命令都直接追加到硬盘, 那么性能完全取决于当前硬盘负载。先写入缓冲区 aof_buf 中, 还有另一个好处, Redis 可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡。
文件同步
Redis 提供了多种 AOF 缓存区的文件同步策略,策略涉及到操作系统的 write 函数和 fsync 函数:为了提高文件写入效率,在现代操作系统中,当用户调用 write 函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题,例如:如果计算机停机,内存缓冲区中的数据会丢失。因此系统同时提供了 fsync、fdatasync 等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。
AOF 缓存区的同步文件策略由配置项 appendfsync 控制,具有以下参数类型:
- always:每次写入都要同步 AOF 文件,即:命令写入 aof_buf 后立即调用系统 fsync 函数操作同步到 AOF 文件,fsync 完成后线程返回。这种情况下,每次有写命令都要同步到 AOF 文件,硬盘 IO 成为了性能瓶颈。在一般的 SATA 硬盘上,Redis 只能支持大约几百 TPS 写入,即便是 SSD 固态硬盘,每秒大约也只能处理几万个命令,而且会大大降低 SSD 的寿命。 显然跟 Redis 高性能特性背道而驰,不建议配置。
- no:命令写入 aof_buf 后调用系统 write 函数操作,因为不对 AOF 文件做 fsync 同步,而是由操作系统负责,同步周期通常为 30 秒,但这种同步的时间是不可控的,且缓冲区中堆积的数据会很多,数据安全性无法保证。虽然提升了性能,但数据安全性无法保证。
- everysec(默认),建议使用。命令写入 aof_buf 后调用系统 write 操作,write 完成后线程返回。fsync 同步文件操作由专门的线程每秒调用一次。是前述两种策略的折中,是性能和数据安全性的平衡。
做到兼顾性能和数据安全性。理论上只有在系统突然宕机的情况下丢失 1 秒的数据。
文件重写
为什么要文件重写呢? 因为过大的 AOF 文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。文件重写能够使得 AOF 文件的体积变得更小,从而使得可以更快的被 Redis 加载。
文件重写是指定期重写 AOF 文件,减小 AOF 文件的体积。需要注意的是,AOF 重写是把 Redis 进程内的数据转化为写命令,同步到新的 AOF 文件,不会对旧的 AOF 文件进行任何读取、写入操作。
值得注意的是,文件重写虽然是强烈推荐的,但并不是必须的。即使没有文件重写,数据也可以被持久化并在 Redis 启动的时候导入。因此在一些场景中,会关闭自动的文件重写,然后通过定时任务在每天的某一时刻定时执行。
文件重写之所以能够压缩 AOF 文件,是基于 3 个现实前提:
- 过期的数据不再写入文件。
- 无效的命令不再写入文件。
- 多条命令可以合并为一个。
重写过程分为手动触发和自动触发:
- 手动触发:直接使用 bgrewriteaof 命令,fork 子进程进行具体的工作,父进程仅在 fork 时被阻塞。
- 自动触发:根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定自动触发时机。
- auto-aof-rewrite-min-size:表示运行 AOF 重写时文件最小体积, 默认为 64MB。
- auto-aof-rewrite-percentage:代表当前 AOF 文件空间(aof_current_size) 和上一次重写后 AOF 文件空间(aof_base_size) 的比值。
自动触发时机相当于:
aof_current_size > auto-aof-rewrite-minsize && (aof_current_size - aof_base_size) / aof_base_size >= auto-aof-rewritepercentage 其中,aof_current_size 和 aof_base_size 可以在 info Persistence 统计信息中查看到。
注意,只有当 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 两个参数同时满足时,才会自动触发 AOF 重写,即 bgrewriteaof 命令操作。
Redis 内部进行文件重写的流程:
- 重写期间,主线程并没有阻塞,而是在执行其他的操作命令,依然会向旧的 AOF 文件写入数据,这样能够保证备份的最终完整性,如果数据重写失败,也能保证数据不会丢失。
- 为了把重写期间响应的写入信息也写入到新的文件中,因此也会为子进程保留一个缓冲区,防止新写的文件丢失数据。
- 重写是直接把当前内存的数据生成对应命令,并不需要读取老的 AOF 文件进行分析、命令合并。
- AOF 文件直接采用的文本协议,主要是兼容性好、追加方便、可读性高可认为修改修复。
- 无论是 RDB 还是 AOF 都是先写入一个临时文件,然后通过重命名完成文件的替换。
AOF 的优点:使用 AOF 持久化会让 Redis 变得非常耐久:你可以设置不同的 fsync 策略,比如无 fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据(fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。
AOF 的缺点:
- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间。
- 数据恢复速度相对于 RDB 比较慢。
重启加载
无论是 RDB 还是 AOF 都可用于服务器重启时的数据恢复,执行流程如下图:
上图很清晰的分析了 Redis 启动恢复数据的流程,先检查 AOF 文件是否开启,文件是否存在,再检查 RDB 是否开启,文件是否存在。
AOF 常用配置
- appendonly no:是否开启 AOF。
- appendfilename:AOF 文件名。
- dir ./:RDB 文件和 AOF 文件所在目录。
- appendfsync everysec:fsync 持久化策略。
- no-appendfsync-on-rewrite no:AOF 重写期间是否禁止 fsync。如果开启该选项,可以减轻文件重写时 CPU 和硬盘的负载(尤其是硬盘),但是可能会丢失 AOF 重写期间的数据;需要在负载和安全性之间进行平衡。
- auto-aof-rewrite-percentage 100:文件重写触发条件之一。
- auto-aof-rewrite-min-size 64mb:文件重写触发提交之一。
- aof-load-truncated yes:如果 AOF 文件结尾损坏,Redis 启动时是否仍载入 AOF 文件。
性能问题与解决方案通过上面的分析,我们都知道 RDB 的快照、AOF 的重写都需要 fork,这是一个重量级操作,会对 Redis 造成阻塞。因此为了不影响 Redis 主进程响应,我们需要尽可能降低阻塞。
- 优先使用物理机或者高效支持 fork 操作的虚拟化技术。
- 控制 Redis 实例最大可用内存,fork 耗时跟内存量成正比,线上建议每个 Redis 实例内存控制在 10GB 以内。
- 合理配置 Linux 内存分配策略,避免物理内存不足导致 fork 失败。
- 降低 fork 操作的频率,如适度放宽 AOF 自动触发时机,避免不必要的全量复制等。
Redis M/S 是否开启持久化?
- 极端情况下可以容忍全量数据丢失,那么建议 Master 关闭持久化,Slave 关闭持久化;
- 极端情况下不能容忍全量数据丢失,但可以容忍部分数据丢失,如果内存数据集较小且不会增长建议 Master 开启 RDB,Slave 开启 RDB;如果数据集很大,或不确定数据集增长趋势,建议 Master 关闭持久化,Slave 开启 RDB。开启 RDB 需要 CPU 和磁盘性能保障。如果 Master 关闭持久化,Slave 开启 RDB 需要保证 Slave 的 RDB 不会被 Master 误重启所覆盖,这里提供几种方案:
- 重启脚本包一层命令先网络请求加载备机备份目录下的 RDB 文件后再执行 Start,可以防止误重启,但备机调整部署可能需要调整脚本,主机打开持久化也需要调整脚本。
- 定时将 RDB 文件通过网络 I/O 传给 Master 节点(文件大比较耗时,文件增长需要考虑定时脚本执行间隔,否则会造成持续的网络 I/O),而且也会有一定数据损失。
- 定时备份 Slave 的 RDB 到备份目录,不做任何其他操作,误重启时人工拷贝 RDB 到 Master 节点(会有一定数据损失)。
- 最大限度需要数据无损,建议 Master 开启 AOF,Slave 开启 AOF。开启 AOF 需要 CPU 和磁盘性能保障。开启 AOF 建议 fsync 同步刷盘使用 everysec,自定义脚本在应用空闲时定时做 bgrewrite,bgrewrite 期间增量数据做缓冲。
|