Redis 提供的持久化,可以避免进程退出造成的数据丢失的问题。启动时,通过加载持久化文件,可以恢复之前的数据。Redis 提供了两种持久化的方式:
# RDB
RDB 持久化是把当前进程数据生成快照保存到硬盘的过程。 触发方式有主动触发和手动触发。
# 触发机制
手动触发命令:
- save 阻塞当前 redis 服务器,直到 RDB 完成
- bgsave fork 子进程进行 RDB 操作,阻塞只存在于 fork 期间
除了手动触发,redis 内部还存在自动触发的机制,例如以下场景:
- 使用 save 配置,如:
save m n
,表示 m 秒内数据集存在 n 次修改时自动 bgsave - 从节点执行全量复制,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点
- 执行 debug reload 命令重新加载 redis
- 默认情况执行 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 的写入流程如下:
- 命令追加写入到 aof_buf (缓冲区) 中。
- AOF 缓冲区根据对应策略向硬盘进行同步。
- 随着 AOF 增大需要定期对 AOF 文件重写以压缩。
- 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 重写流程:
- 执行 AOF 重写请求,如果当前正在执行,则不执行并返回;如果正在执行 bgsave,则等待 bgsave 完成后再执行
- 父进程执行 fork 创建子进程
- 主进程 fork 完成后继续响应命令,所有修改依然写入 AOF 缓冲区并根据 appendfsync 策略同步到硬盘。子进程使用写时复制,共享 fork 操作时的内存数据,同时对于新数据,使用‘AOF 重写缓冲区’来保存,复制 AOF 生成期间这部分数据的丢失
- 子进程根据内存快照,按照命令合并规则写入新的 AOF 文件,每次批量写入硬盘大小默认为 32MB,防止单次刷盘数据过多造成硬盘阻塞
- 新 AOF 完成写入后,子进程通知父进程,将 AOF 重写缓冲区数据写入新的 AOF 文件,并替换老文件
# 重启加载
AOF 和 RDB 都可以用于服务器重启时的数据恢复,如果开启了 AOF,则优先加载 AOF 文件,否则加载 RDB 文件。对于损坏的 AOF 文件,会被拒绝启动,如果是结尾不完整,则可以通过 aof-load-truncated 配置来兼容此情况,这样加载 AOF 时会忽略此错误并继续启动。
# 问题定位与优化
# fork 操作
虽然 fork 不需要拷贝父进程的物理内存空间,但会复制父进程的空间内存页表。fork 操作耗时与进程总内存量息息相关,如果使用虚拟化技术会更加耗时。如何改善:
- 使用物理机或高效支持 fork 操作的虚拟化技术
- 控制 redis 实例最大可用内存,建议再 10GB 以内
- 合理配置 Linux 内存分配策略,避免物理内存不足导致 fork 失败
- 降低 fork 操作频率,如适度放宽 AOF 自动触发时机
# 子进程开销
子进程的操作属于 CPU 密集型,不要做绑定单核 CPU 操作,避免和父进程产生单核资源竞争,也要避免和其他 CPU 密集型服务部署在一起,如果部署多个 redis 实例,尽量保证同一时刻只有一个子进程进行重写。
fork 操作虽有写时复制,但在重写过程中如果数据产生了修改,父进程会把要修改的页创建副本,因此如果有大量写入时,要避免进程重写操作。
对于硬盘,同样不要和其他高硬盘负载的服务部署到一起,对于多个实例,可分别配置不同的硬盘来缓解压力。
# AOF 追加阻塞
AOF 常用的同步策略为 everysec,但在硬盘资源繁忙的时候,也会造成主线程阻塞。因为主线程写入缓冲区,AOF 线程每秒执行一次同步磁盘,并记录同步时间,主线程会判断上次 AOF 的同步时间,如果在 2 秒内将直接返回,否则将阻塞直到同步操作完成。
因此同步磁盘如果阻塞了,则会简介导致主线程阻塞。