Redis 持久化存储方案对比与选择
Redis 提供了两种持久化机制,分别是 RDB(Redis Database)和 AOF(Append Only File)。持久化选用哪种方案,要依据具体使用场景来判断。
RDB 是 Redis 默认的持久化方式。它通过将 Redis 数据库的快照保存到磁盘上的一个二进制文件中,实现数据的持久化。RDB 方式的优点是备份速度快,文件体积小,适合用于备份和灾难恢复。缺点是在发生故障时,可能会丢失最后一次快照后的数据。
AOF 是 Redis 的另一种持久化方式,它通过将 Redis 的写操作追加到一个文件中,实现数据的持久化。AOF 方式的优点是可以保证数据的完整性,即使发生故障也可以通过重放日志文件恢复数据。缺点是备份速度较慢,文件体积较大。Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大。
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。
你也可以同时开启两种持久化方式,在这种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
在 Redis 的配置文件中,可以通过设置 save
参数来控制 RDB 的触发条件和频率,通过设置 appendonly
参数来启用 AOF 持久化方式。
- bgsave 的原理是什么?
bgsave 是 Redis 中执行后台持久化操作的命令,它用于生成 RDB 文件。
当执行 bgsave 命令时,Redis 会 fork
创建一个子进程来将当前数据集的快照以 RDB 的格式写入到磁盘上的一个临时 RDB 文件中。这个子进程使用 Copy-on-Write 机制,即在执行持久化期间,子进程会复制 Redis 主进程的内存数据,并在复制过程中对数据进行读取,而不会对主进程的数据进行写操作。
当程序调用了 fork 方法之后,我们就可以通过 fork 的返回值确定父子进程,以此来执行不同的操作:
- fork 函数返回 0 时,意味着当前进程是子进程;
- fork 函数返回非 0 时,意味着当前进程是父进程,返回值是子进程的 pid;
int main() {
if (fork() == 0) {
// child process
} else {
// parent process
}
}
2
3
4
5
6
7
在 fork 的 手册 (opens new window) 中,我们会发现调用 fork 后的父子进程会运行在不同的内存空间中,当 fork 发生时两者的内存空间有着完全相同的内容,对内存的写入和修改、文件的映射都是独立的,两个进程不会相互影响。
除此之外,子进程几乎是父进程的完整副本(Exact duplicate),然而这两个进程在以下的一些方面会有较小的区别:
- 子进程用于独立且唯一的进程 ID;
- 子进程的父进程 ID 与父进程 ID 完全相同;
- 子进程不会继承父进程的内存锁;
- 子进程会重新设置进程资源利用率和 CPU 计时器;
- ……
最关键的点在于父子进程的内存在 fork 时是完全相同的,在 fork 之后进行写入和修改也不会相互影响,这其实就完美的解决了快照这个场景的问题 —— 只需要某个时间点下内存中的数据,而父进程可以继续对自己的内存进行修改,这既不会被阻塞,也不会影响生成的快照。
# 写时拷贝
既然父进程和子进程拥有完全相同的内存空间并且两者对内存的写入都不会相互影响,那么是否意味着子进程在 fork
时需要对父进程的内存进行全量的拷贝呢?假设子进程需要对父进程的内存进行拷贝,这对于 Redis 服务来说基本都是灾难性的,尤其是在以下的两个场景中:
- 内存中存储大量的数据,
fork
时拷贝内存空间会消耗大量的时间和资源,会导致程序一段时间的不可用; - Redis 占用了 10G 的内存,而物理机或者虚拟机的资源上限只有 16G,在这时我们就无法对 Redis 中的数据进行持久化,也就是说 Redis 对机器上内存资源的最大利用率不能超过 50%;
如果无法解决上面的两个问题,使用 fork
来生成内存镜像的方式也无法真正落地,不是一个工程中真正可以使用的方法。
就算脱离了 Redis 的场景,
fork
时全量拷贝内存也是难以接受的,假设我们需要在命令行中执行一个命令,我们需要先通过fork
创建一个新的进程再通过exec
来执行程序,fork
拷贝的大量内存空间对于子进程来说可能完全没有任何作用的,但是却引入了巨大的额外开销。
写时拷贝(Copy-on-Write)的出现就是为了解决这一问题,就像我们在这一节开头介绍的,写时拷贝的主要作用就是将拷贝推迟到写操作真正发生时,这也就避免了大量无意义的拷贝操作。在一些早期的 *nix 系统上,系统调用 fork
确实会立刻对父进程的内存空间进行复制,但是在今天的多数系统中,fork
并不会立刻触发这一过程:
在 fork
函数调用时,父进程和子进程会被 Kernel 分配到不同的虚拟内存空间中,所以在两个进程看来它们访问的是不同的内存:
- 在真正访问虚拟内存空间时,Kernel 会将虚拟内存映射到物理内存上,所以父子进程共享了物理上的内存空间;
- 当父进程或者子进程对共享的内存进行修改时,共享的内存才会以页为单位进行拷贝,父进程会保留原有的物理空间,而子进程会使用拷贝后的新物理空间;
在 Redis 服务中,子进程只会读取共享内存中的数据,它并不会执行任何写操作,只有父进程会在写入时才会触发这一机制,而对于大多数的 Redis 服务或者数据库,写请求往往都是远小于读请求的,所以使用 fork
加上写时拷贝这一机制能够带来非常好的性能,也让 BGSAVE
这一操作的实现变得非常简单。
# 总结
Redis 实现后台快照的方式非常巧妙,通过操作系统提供的 fork
和写时拷贝的特性轻而易举的就实现了这个功能,从这里我们就能看出作者对于操作系统知识的掌握还是非常扎实的,大多人在面对类似的场景时,想到的方法可能就是手动实现类似『写时拷贝』的特性,然而这不仅增加了工作量,还增加了程序出现问题的可能性。
到这里,我们简单总结一下 Redis 为什么在使用 RDB 进行快照时会通过子进程的方式进行实现:
- 通过
fork
创建的子进程能够获得和父进程完全相同的内存空间,父进程对内存的修改对于子进程是不可见的,两者不会相互影响; - 通过
fork
创建子进程时不会立刻触发大量内存的拷贝,内存在被修改时会以页为单位进行拷贝,这也就避免了大量拷贝内存而带来的性能问题;
具体的执行过程如下:
- 主进程执行 bgsave 命令后,会创建一个子进程。
- 子进程会复制主进程的内存数据,并在复制过程中对数据进行读取。
- 子进程将复制的数据写入到一个临时 RDB 文件中。
- 当子进程完成 RDB 文件的写入后,会用该文件替换原有的 RDB 文件,完成持久化操作。
- 主进程继续处理其他命令请求,而不会被持久化过程所阻塞。
通过使用子进程进行持久化操作,可以避免主进程在持久化过程中被阻塞,从而保持 Redis 的高性能和低延迟。此外,子进程和主进程之间共享文件描述符,因此不需要进行数据复制,也减少了内存的使用。
需要注意的是,bgsave 命令是一个异步操作,它不会阻塞主进程的执行。因此,在执行 bgsave 命令后,可以继续执行其他命令,而不需要等待持久化操作完成。
为什么 Redis 快照使用子进程 - 面向信仰编程 (opens new window)
# 一、是否该选择 Redis 持久化
适用情形
- 当你需要把 Redis 当作数据库、缓存以及消息队列中间件(DB、Cache、MQ)来用,并且希望在重启后能恢复数据,这时就可以考虑使用 Redis 持久化。
- 若业务对数据完整性有较高要求,比如支付、订单这类场景,持久化是必不可少的。
不适用情形
- 如果 Redis 仅被用作纯缓存,而且数据可以通过后端数据库重新生成,那么持久化就不是必需的。
- 要是业务更看重写入性能,对数据丢失不太敏感,像统计类业务,那么可以不进行持久化。
# 二、持久化方案对比与建议
# 持久化
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。
一般来说,如果想达到足以媲美 PostgreSQL 的数据安全性,你应该同时使用两种持久化功能。如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化,但并不推荐这种方式:因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免之前提到的 AOF 程序的 bug。
# RDB 持久化
将某个时间点的所有数据都存放到硬盘上。
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
如果数据量很大,保存快照的时间会很长。
原理:在特定的时间间隔内,将内存中的数据集快照写入磁盘。
优点:
- 生成的文件紧凑,适合用于备份和灾难恢复。
- 对 Redis 性能的影响较小,因为是 fork 子进程来完成持久化操作。
- 恢复数据的速度比 AOF 快。
缺点:
- 无法做到实时持久化,可能会丢失最后一次快照之后的数据。
- fork 子进程时会占用一定的内存和 CPU 资源。
适用场景:
- 适用于可以容忍几分钟数据丢失的场景。
- 比较适合做冷备和灾难恢复。
配置建议:
save 900 1 # 900秒(15分钟)内有1个key发生变化,就执行快照 save 300 10 # 300秒(5分钟)内有10个key发生变化,就执行快照 save 60 10000 # 60秒内有10000个key发生变化,就执行快照
1
2
3
# AOF 持久化
原理:把每一个写操作追加到日志文件中,重启时重新执行这些命令来恢复数据。
优点:
- 数据安全性更高,支持不同的同步策略,如每秒同步、每次写操作同步等。
- 日志文件是增量追加的,不会出现文件损坏的问题。
缺点:
- AOF 文件通常比 RDB 文件大。
- 在某些同步策略下,写操作的性能会有所下降。
- 恢复数据的速度比 RDB 慢。
适用场景:
- 适用于对数据完整性要求较高的场景。
- 适合用作热备。
配置建议:
appendonly yes # 开启AOF appendfsync everysec # 每秒同步一次,兼顾性能和安全性 no-appendfsync-on-rewrite yes # 在重写AOF文件时不进行同步,提高性能 auto-aof-rewrite-percentage 100 # 当AOF文件大小比上次重写增长100%时,触发重写 auto-aof-rewrite-min-size 64mb # AOF文件最小重写大小
1
2
3
4
5
将写命令添加到 AOF 文件(Append Only File)的末尾。
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:
选项 | 同步频率 |
---|---|
always | 每个写命令都同步 |
everysec | 每秒同步一次 |
no | 让操作系统来决定何时同步 |
- always 选项会严重减低服务器的性能;
- everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
- no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。
随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
# 混合持久化(RDB + AOF)
在 Redis 4.0 及以后的版本中,混合持久化(RDB+AOF)确实是大多数场景下的最佳选择,但并非适用于所有情况。以下是具体分析和建议:
混合持久化结合了 RDB 和 AOF 的优势:
- 恢复速度快:重启时优先加载 RDB 快照(二进制格式,加载效率高)。
- 数据安全性高:RDB 之后的增量操作通过 AOF 日志恢复,减少数据丢失。
- 文件体积更小:AOF 重写时仅保存 RDB 快照 + 增量日志,避免 AOF 文件过大。
适用场景:
- 既需要快速恢复,又希望尽量减少数据丢失的场景(如缓存 + 持久化存储)。
- 生产环境中对数据完整性和可用性要求较高的场景。
# 二、不适合混合持久化的场景
纯缓存场景
如果 Redis 仅用作缓存,数据可从后端数据库重建,无需持久化(关闭 RDB 和 AOF)。- 原因:持久化会增加磁盘 I/O 开销,降低性能。
对恢复速度要求极高
如果需要极快的恢复速度(如秒杀系统),且能接受几分钟的数据丢失,仅使用 RDB。- 原因:RDB 加载速度比混合持久化更快(无需执行 AOF 命令)。
磁盘空间有限或写入性能敏感
如果磁盘空间紧张或写入操作频繁(如高频统计系统),仅使用 RDB。- 原因:AOF(尤其是混合模式)会增加磁盘占用和写入负担。
需要兼容旧版本 Redis
混合持久化的 AOF 文件格式不兼容 Redis 4.0 之前的版本,若需降级或迁移,需谨慎选择。
# 三、配置建议总结
场景 | 持久化方案 | 关键配置参数 |
---|---|---|
缓存(可接受数据丢失) | 关闭持久化 | save "" appendonly no |
快速恢复(可接受少量丢失) | 仅 RDB | save 60 10000 appendonly no |
高安全性(数据不可丢失) | 混合持久化(默认推荐) | appendonly yes aof-use-rdb-preamble yes |
高安全性 + 高性能写入 | AOF(everysec) | appendonly yes appendfsync everysec |
# 四、最佳实践
定期备份 RDB 文件
无论使用哪种持久化方案,都建议定期(如每天)备份 RDB 快照到远程存储,以防磁盘故障。监控磁盘空间和性能
- AOF 文件会不断增长,需通过
auto-aof-rewrite-percentage
控制重写频率。 - 混合模式下,RDB 快照会增加 AOF 文件的初始体积,需预留足够磁盘空间。
- AOF 文件会不断增长,需通过
测试恢复流程
在生产环境部署前,务必测试 Redis 重启后的恢复流程,确保数据能正确恢复。
# 结论
混合持久化(RDB+AOF)是 4.0 + 版本的首选方案,但需根据业务需求权衡:
- 优先选混合:若需要兼顾恢复速度和数据安全性。
- 选纯 RDB:若对恢复速度要求极高,且能接受数据丢失。
- 选纯 AOF:若对数据完整性要求苛刻(如金融场景),那么建议开启 AOF,并采用
everysec
的同步策略。 - 关闭持久化:若仅用作缓存。