Redis 提供的持久化,可以避免进程退出造成的数据丢失的问题。启动时,通过加载持久化文件,可以恢复之前的数据。Redis 提供了两种持久化的方式:

# RDB

RDB 持久化是把当前进程数据生成快照保存到硬盘的过程。 触发方式有主动触发和手动触发。

# 触发机制

手动触发命令:

  • save 阻塞当前 redis 服务器,直到 RDB 完成
  • bgsave fork 子进程进行 RDB 操作,阻塞只存在于 fork 期间

除了手动触发,redis 内部还存在自动触发的机制,例如以下场景:

  1. 使用 save 配置,如: save m n ,表示 m 秒内数据集存在 n 次修改时自动 bgsave
  2. 从节点执行全量复制,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点
  3. 执行 debug reload 命令重新加载 redis
  4. 默认情况执行 shutdown 命令时,没有开启 AOF 则会自动执行 bgsave

# 文件处理

RDB 文件保存在 dir 配置的目录下,可通过如下命令在运行期间动态指定:

config set dir {newDir}
config set dbfilename {newFileName}

RDB 文件默认采用 LZF 算法进行压缩,可以大幅降低 RDB 文件大小。在启动时,如果 RDB 文件校验失败,redis 将拒绝启动。

# 优缺点

优点

  • 文件紧凑,代表某一个时间节点上的数据快照,适合全量复制、备份等场景
  • 加载恢复速度远快于 AOF
    缺点
  • 无法实时持久化,bgsave 的 fork 操作属于重量级操作,频繁执行成本高
  • RDB 使用二进制保存,有版本兼容性问题

# AOF

AOF 使用独立日志的方式记录每次执行的命令,解决了实时持久化的问题。AOF 需要设置 appendonly yes 开启。AOF 的写入流程如下:

  1. 命令追加写入到 aof_buf (缓冲区) 中。
  2. AOF 缓冲区根据对应策略向硬盘进行同步。
  3. 随着 AOF 增大需要定期对 AOF 文件重写以压缩。
  4. redis 服务器重启时,可以加载 AOF 文件进行恢复。

采用文本协议格式,让兼容性更好,且读取时避免多一次的转换操作,同时具有可读性。写入时追加到 aof_buf 可以让性能更好,尤其在硬盘负载较高的情况下。

# 文件同步

redis 提供了不同的 AOF 缓冲区同步策略,由参数 appendfsync 控制,可选值如下:

可配置值说明
always命令写入 aof_buf 后调用系统 fsync 操作同步 AOF 文件,fsync 完成后线程返回
everysec命令写入 aof_buf 后调用系统 write 操作,write 完成后线程返回,fsync 同步文件操作由专门线程每秒调用一次
no命令写入 aof_buf 后调用系统 write 操作,不对 AOF 文件做 fsync 同步,由操作系统负责,最长周期为 30 秒

通常建议使用 everysec, 这样可以兼顾性能和安全,理论上最多丢失 1 秒的数(不完全正确)

# 重写机制

随着 AOF 不断写入,体积将越来越大,因此需要进行重写来缩小文件大小。重新能变小的原因,在于重写后 AOF 只保留最终的数据写入命令,过期的数据不再写入,同时能合并一些命令和删除一些无效的命令。合并时为了避免命令过大造成客户端缓冲区溢出,对于 list、set、hash、zset 等操作,以 64 个元素为界拆分为多条。

重写也分为手动和自动触发:

  • 手动触发:直接调用 bgrewriteaof 命令
  • 自动触发:根据 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 重写流程:

  1. 执行 AOF 重写请求,如果当前正在执行,则不执行并返回;如果正在执行 bgsave,则等待 bgsave 完成后再执行
  2. 父进程执行 fork 创建子进程
  3. 主进程 fork 完成后继续响应命令,所有修改依然写入 AOF 缓冲区并根据 appendfsync 策略同步到硬盘。子进程使用写时复制,共享 fork 操作时的内存数据,同时对于新数据,使用‘AOF 重写缓冲区’来保存,复制 AOF 生成期间这部分数据的丢失
  4. 子进程根据内存快照,按照命令合并规则写入新的 AOF 文件,每次批量写入硬盘大小默认为 32MB,防止单次刷盘数据过多造成硬盘阻塞
  5. 新 AOF 完成写入后,子进程通知父进程,将 AOF 重写缓冲区数据写入新的 AOF 文件,并替换老文件

# 重启加载

AOF 和 RDB 都可以用于服务器重启时的数据恢复,如果开启了 AOF,则优先加载 AOF 文件,否则加载 RDB 文件。对于损坏的 AOF 文件,会被拒绝启动,如果是结尾不完整,则可以通过 aof-load-truncated 配置来兼容此情况,这样加载 AOF 时会忽略此错误并继续启动。

# 问题定位与优化

# fork 操作

虽然 fork 不需要拷贝父进程的物理内存空间,但会复制父进程的空间内存页表。fork 操作耗时与进程总内存量息息相关,如果使用虚拟化技术会更加耗时。如何改善:

  1. 使用物理机或高效支持 fork 操作的虚拟化技术
  2. 控制 redis 实例最大可用内存,建议再 10GB 以内
  3. 合理配置 Linux 内存分配策略,避免物理内存不足导致 fork 失败
  4. 降低 fork 操作频率,如适度放宽 AOF 自动触发时机

# 子进程开销

子进程的操作属于 CPU 密集型,不要做绑定单核 CPU 操作,避免和父进程产生单核资源竞争,也要避免和其他 CPU 密集型服务部署在一起,如果部署多个 redis 实例,尽量保证同一时刻只有一个子进程进行重写。

fork 操作虽有写时复制,但在重写过程中如果数据产生了修改,父进程会把要修改的页创建副本,因此如果有大量写入时,要避免进程重写操作。

对于硬盘,同样不要和其他高硬盘负载的服务部署到一起,对于多个实例,可分别配置不同的硬盘来缓解压力。

# AOF 追加阻塞

AOF 常用的同步策略为 everysec,但在硬盘资源繁忙的时候,也会造成主线程阻塞。因为主线程写入缓冲区,AOF 线程每秒执行一次同步磁盘,并记录同步时间,主线程会判断上次 AOF 的同步时间,如果在 2 秒内将直接返回,否则将阻塞直到同步操作完成。

因此同步磁盘如果阻塞了,则会简介导致主线程阻塞。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

HuaLin 微信支付

微信支付

HuaLin 支付宝

支付宝