type
status
date
slug
summary
tags
category
password

1、概述

Redis是一个内存数据库,所有的数据将保存在内存中,这与传统的 MySQL、Oracle、SqlServer 等关系型数据库直接把数据保存到硬盘相比,Redis的读写效率非常高。但是保存在内存中也有一个很大的缺陷,一旦断电或者宕机,内存数据库中的内容将会全部丢失。为了弥补这一缺陷,Redis 提供了把内存数据持久化到硬盘文件,以及通过备份文件来恢复数据的功能,即 Redis 的持久化机制。有两种方式:
持久化方式
描述
优点
缺点
适合场景
AOF
记录所有写操作命令,以追加方式写入文件,重启时重新执行这些命令来恢复数据
数据安全,详细记录到每一条写指令
文件体积大,数据恢复速度慢
文件小,恢复速度快,适合用于定时快照备份和主从全量数据同步的场景。
RDB
按照指定时间间隔生成 Redis 数据库快照,这些快照保存了某个时间点的全量 Redis 数据,重启后使用数据库快照来恢复数据
文件体积小,便于快速备份和恢复,启动速度快
数据安全性低,隔一段时间才持久化一次,宕机容易造成数据的丢失
文件大,但是大部分情况保留了原始的命令操作,适合用于对数据完整性和安全性更高的场景中。
另外还有一种混合模式,同时开启 AOF 和 RDB 功能:
  1. RDB 以一定的频率执行。
  1. 在两次 RDB 期间,使用 AOF 日志记录这期间的所有命令操作。
  1. 下一次 RDB 的时候,清空 AOF 日志。
  1. 在恢复数据的时候,会优先使用 AOF 日志来恢复数据,因为 AOF 保存的文件比 RDB 文件更完整。
notion image

2、AOF

Redis 每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里,然后重启 Redis 的时候,先去读取这个文件里的命令,并且执行它,执行完文件的所有命令后数据库就被还原了,这就是 AOF 功能。
注意只会记录写操作命令,读操作命令是不会被记录的。

2.1 开启AOF

第一种方式,通过配置文件开启(推荐),修改 Redis 配置文件 redis.conf
第二种方式,可以通过 Redis 客户端临时开启 AOF(重启后会失效):
💡
AOF 文件默认保存在 Redis 的工作目录(由 dir 配置指定),文件名为 appendonly.aof

2.2 AOF的工作过程

AOF 的操作步骤为:
  1. 命令追加(append):当AOF持久化功能打开时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。(先写内存,再写日志。这样可以保证只有正确执行的命令才会记录到日志上。)
  1. 文件的写入和同步(sync):根据配置的 appendfsync 同步刷盘策略决定何时调用 Linux 的 fsyncfdatasync 函数将 aof_buf 缓冲区的内容保存到磁盘的 AOF 文件中。

2.3 AOF的同步策略

AOF 提供三种同步策略,通过 appendfsync 配置:
配置项
写回时机
优点
缺点
Always
每次执行完一个命令之后, write 和 sync 都会被执行。也就是说将aof_buf缓冲区的所有内容写入并同步到AOF文件,每个写命令同步写入磁盘
可靠性高,数据基本不丢失
性能较差
Everysec
默认策略,sync 每隔一秒钟就会执行一次。也就是说将aof_buf缓存区的内容写入AOF文件,每秒同步一次,该操作由一个线程专门负责。
性能适中
宕机丢失1s的数据
No
write 都会被执行, 但 sync 会被略过。也就是说将aof_buf缓存区的内容写入AOF文件,什么时候同步由操作系统来决定
性能好
宕机丢失数据多

2.4 AOF重写机制

AOF 日志是一个文件,随着执行的写操作命令越来越多,文件的大小会越来越大。如果当 AOF 日志文件过大就会带来性能问题,比如重启 Redis 后,需要读 AOF 文件的内容以恢复数据,如果文件过大,整个恢复的过程就会很慢。
为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 重写机制。当 AOF 文件的大小超过所设定的阈值后,Redis 就会启用 AOF 重写机制,来压缩 AOF 文件。
AOF 重写的触发条件:
  • 手动触发:执行 BGREWRITEAOF 命令
  • 自动触发:根据配置的 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size
AOF 重写过程如下:
  1. 主线程 fork 出一个 bgrewriteaof 子进程,这里的 fork() 使用写时拷贝(copy-on-write)的技术,拷贝出一份数据副本,并建立一个 AOF 重写缓冲区
  1. 子进程读取当前数据库状态,生成新的 AOF 文件。注意:AOF 重写不会去读旧的 AOF 文件,而是读取当前数据库中的所有键值对,然后为每一个键值生成一条命令记录到新的 AOF 文件中。
    1. notion image
  1. 子线程重写期间,如果服务器收到了写命令,主线程会先把写命令写到 AOF 缓冲区(这部分数据会写到旧的 AOF 文件,这样保证即使重写失败也能保证数据的安全),然后再写到 AOF 重写缓冲区。
  1. 子进程重写完成后,通知主线程,主线程收到信号后会把 AOF 重写缓冲区的内容写到新的 AOF 文件(不会直接写到新的 AOF 文件里面,而是先写 AOF 缓冲区,再写回新的 AOF 文件),最后把新的 AOF 文件进行改名,覆盖原有的 AOF 文件。
整个过程只有最后一步是阻塞主线程的,这将 AOF 重写对性能造成的影响降到了最低。
notion image
这里涉及到两个知识点:
  • AOF 重写缓冲区
  • 写时复制(Copy-On-Write)技术
AOF 重写缓冲区是什么
重写 AOF 日志过程中,主进程会继续处理客户端的写命令。如果主进程修改了已经存在 key-value,此时子进程的内存数据就跟主进程的内存数据不一致了。为了解决这种数据不一致问题,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在创建 bgrewriteaof 子进程之后开始使用。
在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」。AOF 缓冲区和 AOF 重写缓冲区的区别:
  • AOF 缓冲区:用于临时存储所有写命令,所有的数据先写到 AOF 缓冲区再根据策略同步到磁盘上的 AOF 文件。
  • AOF 重写缓冲区:仅在重写期间存在,专门用于存储 AOF 重写期间的新写命令。
联系:当 AOF 重写完成时,主进程在把 AOF 重写缓冲区的数据写到 AOF 缓冲区,再同步到 AOF 文件中。在 AOF 重写过程中,AOF 重写缓存区会一直保持从重写开始到结束所有写命令,而 AOF 缓存区会不断重复【写入—>更新到 AOF 文件—>清除—>写入】这一过程,因而只会保存一部分的命令,所以两者的内容不一致。
写时复制(Copy-On-Write)技术是什么
Redis 在执行 BGSAVE 命令或者 BGREWRITEAOF 命令时,需要 fork 出子进程,调用了 Linux 的 fork() 命令,使用了 COW 技术。
COW 的核心思想是:多个进程可以共享同一资源的只读副本,只有当某个进程需要修改这个资源时,系统才会真正执行复制操作,为该进程创建独立的可写副本
  1. 父进程使用 COW 技术 fork 出子线程的时候,kernel 把父进程中所有的内存页的权限都设为 read-only,然后子进程的地址空间指向父进程,子线程和父线程共享物理内存空间。
  1. 当父进程写数据的时候,在写入数据的内存页上触发页中断异常,CPU 硬件检测到内存页是 read-only 的,于是触发页异常中断(page-fault),陷入 kernel 的一个中断例程。中断例程中,kernel 就会把触发的异常的页复制一份。这时候主进程再把要修改的数据写入到新的物理页,子进程仍然指向旧的物理页数据(说明子线程备份的是旧数据)。
notion image
注意:
  • fork() 完之后,父子进程只是分配的虚拟内存空间不同,对应的物理内存空间是相同的。只有父进程会在写入时才会触发 COW 机制。没有触发异常的其他内存页依然是父子进程共享同一个物理页。
  • 子进程写到备份文件的数据是执行了 fork() 函数那一时刻的数据,后面的更新都不会备份。
  • 总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断 page-fault),这样就得耗费不少性能在复制上。而在 rehash 阶段上,写操作是无法避免的。所以 Redis 在 fork 出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。
优点:
  • 可以快速完成线程 fork 工作,不会阻塞主线程太长时间。
  • 可以减少不必要的内存资源分配。
缺点:
  • 写操作可能比传统方式稍慢(需要先复制)
  • 父线程如果产生大量写操作,会产生大量的页中断异常,也会造成性能损失。
应用场景:
  • Btrfs 和 ZFS 等现代文件系统
  • Docker 等容器平台的文件系统(如 aufs、overlayfs)

3、RDB

RDB 通过创建某个时间点的数据快照(RDB 文件)来实现持久化。RDB 文件是压缩的二进制文件,默认名为 dump.rdb,保存着 Redis 某个时间点的全量数据。
注意:执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。如果频率太低,服务器故障时,丢失的数据会更多。通常可能设置至少 5 分钟才保存一次快照,这时如果 Redis 出现宕机等情况,则意味着最多可能丢失 5 分钟数据。

3.1 开启RDB

第一种方式,通过修改 redis.conf 配置文件自动触发。底层通过服务器周期函数 serverCron 来触发,该函数每 100 毫秒执行一次。默认配置如下所示
第二种方式,使用 Redis 命令手动触发。Redis 提供了两个命令来生成 RDB 文件,分别是 SAVE 和 BGSAVE,他们的区别就在于是否在「主线程」里执行:
  • SAVE 命令:在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程
  • BGSAVE 命令:会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞
验证 RDB 是否工作
在输出中查找与RDB相关的信息,如:
  • rdb_last_save_time:上次成功保存的时间戳
  • rdb_last_bgsave_status:上次后台保存状态

3.2 RDB的工作过程

不管是自动触发还是手动触发创建 RDB 文件,最终都会调用 rdbSave 函数来创建 RDB 文件。
以最常用的 BGSAVE 命令为例,执行过程如下:
  1. 主线程 fork 出一个 BGSAVE 子进程,这里的 fork() 同样使用写时拷贝(copy-on-write)的技术,拷贝出一份数据副本。
  1. 子进程把数据写到一个临时的 RDB 文件。
  1. 当子进程写完新的 RDB 文件后,把旧的 RDB 文件替换掉。
notion image
 
注意:
  • 为了避免产生竞争条件, BGSAVE 执行时,SAVE 命令不能执行。
  • 为了避免性能问题, BGSAVEBGREWRITEAOF 不能同时执行。
  • RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。
  • 调用 rdbLoad 函数载入 RDB 文件时,不能进行任何和数据库相关的操作,不过订阅与发布方面的命令可以正常执行,因为它们和数据库不相关联。
  • 如果服务器在启动时, 打开了 AOF 功能, 那么程序优先使用 AOF 文件来还原数据。 只有在 AOF 功能未打开的情况下, Redis 才会使用 RDB 文件来还原数据。这是因为在服务器发生故障时,RDB 丢失的数据会比 AOF 持久化的方式更多,因为 RDB 快照是全量快照的方式,因此执行的频率不能太频繁,否则会影响 Redis 性能,而 AOF 日志可以以秒级的方式记录操作命令,所以丢失的数据就相对更少。
Redis系列:事件机制和线程模型Redis系列:键空间和内存策略
Loading...