Appearance
Redis 持久化机制
Redis 持久化机制是指将内存中的数据保存到磁盘,以保证数据在服务重启或故障后不会丢失。
Redis有两种持久化机制:RDB和AOF。
1.RDB
1.1 概述与工作原理
RDB全称Redis Database,是指如果在指定的时间间隔后,达到指定的修改次数后,会生成数据快照(Snapshot),并保存到磁盘的dump.rdb文件中。
RDB的工作原理如下:
- Redis 执行
fork()创建子进程,由子进程负责持久化工作,父进程接收请求执行命令; - 子进程将内存数据写入到临时RDB文件中;
- 当子进程完成写入工作后,替换旧的RDB文件;
由于写时复制技术,父子进程在 fork 后共享内存页。只有当父进程需要修改某个内存页时,才会复制该页,从而避免在快照期间复制所有内存,减少内存消耗。
关于fork()
fork()方法(或fork()系统调用)是类 Unix 操作系统(包括 Linux、macOS 等)中一个非常核心且强大的操作,它的作用是 创建一个新的进程,它是当前进程的精确副本。这个新的进程被称为 子进程(Child Process),而调用
fork()的原始进程则被称为 父进程(Parent Process)。当父进程调用
fork()后,操作系统会创建一个几乎与父进程完全相同的子进程。这里的“相同”意味着子进程会继承父进程的大部分属性,包括:
- 代码段(Text Segment):子进程和父进程共享相同的代码。
- 数据段(Data Segment):子进程获得父进程数据段的副本。通常采用 写时复制(Copy-on-Write, COW) 机制。这意味着最初父子进程共享物理内存页,只有当任一方尝试修改这些数据时,操作系统才会为修改方创建一份私有的副本。这大大提高了
fork()的效率。- 栈(Stack):子进程获得父进程栈的副本。
- 环境变量:子进程继承父进程的环境变量。
- 打开的文件描述符:子进程继承父进程所有打开的文件描述符(例如标准输入、标准输出、错误输出,以及其他已打开的文件)。这意味着父进程打开的任何文件,子进程也都可以直接读写。
- 当前工作目录:子进程继承父进程的当前工作目录。
- 信号处理设置:子进程继承父进程的信号处理设置。
fork()系统调用有一个非常特殊的返回值:
- 在父进程中:
fork()返回 子进程的 PID。- 在子进程中:
fork()返回 0。- 如果创建失败:
fork()返回 -1,并且设置errno来指示错误原因。
1.2 RDB 核心配置
在Redis配置文件中,关于RDB的核心配置如下:
save <seconds> <changes> [<seconds> <changes> ...]:表示自上次持久化,经过<seconds>秒后,在这段时间内,修改数据库的次数达到<changes>后,就会再次进行持久化,生成数据快照并保存到磁盘中。例如:txtsave 3600 1 300 100 60 10000- 3600秒内(1小时),有1次变化就进行持久化;
- 300秒内(5分钟),有100次变化就进行持久化;
- 60秒内(1分钟),有10000次变化就进行持久化;
如果要禁用RDB持久化,只需要在
save后写上空字符串,如下:txtsave ""dir ./:配置dump.rdb文件保存的目录;dbfilename dump.rdb:配置持久化文件的名称,默认就是dump.rdb;
1.3 触发RDB快照
在Rrdis中,触发RDB快照的方式如下:
- 当启用了RDB,并且达到了持久化条件,就会自动进行持久化,生成数据快照;
- 当执行
flushdb或flushall命令时,会立即进行持久化,生成数据快照,但是此时某个数据库或全部数据库是空的,没有意义; - 当执行
shutdown命令时,会立即进行持久化,生成数据快照; - 通过手动发出命令触发RDB进行持久化:
save:会阻塞 Redis 服务器进程,直到 RDB 文件完全创建完成。在此期间,Redis 不能处理任何其他命令请求,客户端发送的所有命令都会被拒绝。在生产环境中禁用!bgsave:会派生(fork)一个子进程来执行 RDB 快照的创建。父进程(Redis 主进程)会继续处理客户端请求,不会被阻塞。推荐在生产环境手动触发快照时使用。
1.4 RDB优缺点
RDB的优点如下:
- RDB 是 Redis 数据库在某一时间点的非常紧凑的单一文件表示,非常适合用于备份;
- RDB 对于灾难恢复非常出色,因为它是一个紧凑的单一文件,可以轻松地传输到远距离的数据中心,或上传到 Amazon S3(可以加密);
- RDB 能够最大限度地提升 Redis 的性能,因为 Redis 父进程在持久化数据时唯一需要做的工作就是派生(fork)一个子进程来完成所有剩余任务,父进程本身永远不会执行磁盘 I/O 或类似操作;
- 与 AOF(Append Only File)相比,RDB 允许在处理大型数据集时实现更快的重启;
RDB的缺点如下:
- 可能会有数据丢失的危险:RDB文件是在设定的时间点达到修改次数后才会自动生成,如果在这段时间内发生了断电、服务器崩溃等问题,会丢失最近几分钟的数据;
- RDB 需要频繁地
fork()来通过子进程将数据持久化到磁盘。如果数据集很大,fork()操作可能会非常耗时,并可能导致 Redis 停止服务客户端几毫秒,甚至数据集非常大且 CPU 性能不佳时可能停止一秒钟。AOF(Append Only File)也需要fork(),但频率较低,并且可以调整重写日志的频率,而不会在持久性方面做出任何权衡。
2. AOF
2.1 概述与工作原理
AOF,全称Append Only File,当开启了AOF配置后,Redis接收到写指令后(例如,set命令),会将其追加到AOF文件,当Redis重启时,会重新执行AOF文件中的指令来恢复数据。
当 Redis 服务器执行了任何会修改数据集的命令(例如 SET、LPUSH、SADD、DEL 等)时,这些命令会以 Redis 命令协议的格式(RESP 协议)被追加到 AOF 缓冲区(aof_buf)中。命令不是立即写入磁盘,而是先进入缓冲区。之后,缓冲区中的命令会根据文件同步策略,调用fsync()写入磁盘中。文件同步策略如下:
no:不主动进行同步,数据何时写入磁盘取决于操作系统。这种模式下,数据安全性最低,但性能最好。如果服务器宕机,可能会丢失几秒钟的数据。always:每个命令都会立即同步到磁盘。数据安全性最高,但性能最低,因为每次写入都会进行一次fsync调用。everysec:(默认且推荐) 每秒同步一次。这在性能和数据安全性之间取得了很好的平衡。即使服务器宕机,最多也只会丢失 1 秒的数据。
2.2 AOF核心配置
有关AOF的核心配置如下:
appendonly:是否开启AOF,默认值为
no表示关闭,可设置如下开启AOF:txtappendonly yesappenddirname:AOF文件保存目录;
appendfilename:AOF文件前缀名,自Redis 7之后,AOF文件包括三类:
- base 文件(基础文件):表示某个时间点的数据库快照,base文件格式有可能是RDB(二进制序列化)或AOF(文本命令)的。只有一个。
- increment 文件(增量文件):记录自base文件创建后,Redis接收到的写命令。可能会有多个。
- manifest 文件(清单文件):记录AOF文件包括哪些,以及increment文件的顺序。
例如,下面就是一个AOF文件的例子:
txtappendonly.aof.1.base.rdb appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof appendonly.aof.manifestappendfsync:缓冲区同步策略,即决定缓冲区的内容何时写入磁盘,配置如下:
txt# appendfsync always appendfsync everysec # appendfsync noaof-use-rdb-preamble:AOF文件中的base文件格式可能是RDB或AOF,该项配置决定base文件使用RDB格式,默认值为
yes表示开启。txtaof-use-rdb-preamble yes
2.3 AOF重写
随着时间的推移,AOF 文件会变得越来越大。因为 AOF 记录了所有的写命令,包括许多过期、删除或者被后续操作覆盖的命令。例如,SET key value1 后又 SET key value2,那么 value1 的 SET 命令在 AOF 中就变成了冗余。
AOF 重写机制就是为了解决这个问题,它会创建一个新的 AOF 文件,这个新文件只包含恢复当前数据集所需的最少命令序列。
AOF 重写可以自动触发,也可以手动触发。
自动触发:Redis会在满足以下两个条件时自动触发AOF重写
- 当前 AOF 文件(increment文件)大小与上次重写后的 AOF 文件大小之比达到设定的百分比。
- 当前 AOF 文件(increment文件)大小达到设定的最小尺寸。
上面两个参数可以配置如下:
txt
auto-aof-rewrite-percentage 100 # 当前 AOF 文件比上次重写后大 100% (即达到两倍)
auto-aof-rewrite-min-size 64mb # AOF 文件至少达到 64MB 时才触发重写手动触发:通过 BGREWRITEAOF 命令手动触发 AOF 重写。
在Redis 7及之后,重写的工作流程如下:
- Redis调用
fork()创建子进程; - 子进程在临时文件中写新的基础文件(base file);
- 父进程创建一个新的增量文件(increment file)来记录写指令;
- 当子进程完成后(写新的基础文件),父进程获得信号,使用新的基础文件(子进程创建)和新的增量文件(父进程创建)来创建临时清单文件(manifest file);
- Redis使用原子操作交换清单文件(新的和旧的),保证AOF重写生效;之后会清除没用的基础文件和增量文件;
2.4 AOF优缺点
AOF的优点如下:
使用 AOF(Append Only File)能让 Redis 拥有更高的数据持久性。
可以配置不同的
fsync策略:完全不进行fsync、每秒进行一次fsync,或者每个查询都进行fsync。在默认的“每秒fsync一次”策略下,写入性能依然非常出色。fsync操作由一个后台线程执行,主线程会尽量在没有fsync操作进行时执行写入,因此最多只会丢失一秒钟的写入数据。AOF 日志是一个**只追加(append-only)**的日志,所以没有寻道操作,即使发生断电也不会出现数据损坏问题。即使日志因为某些原因(比如磁盘已满或其他原因)以一个不完整的命令结尾,
redis-check-aof工具也能轻松修复它。当 AOF 文件变得过大时,Redis 能够在后台自动重写 AOF 文件。重写过程是完全安全的。
AOF 包含了一个日志,其中所有操作都以易于理解和解析的格式一个接一个地记录。甚至可以轻松地导出 AOF 文件。例如,即使不小心使用了
FLUSHALL命令清空了所有数据,只要在此期间没有进行日志重写,你仍然可以通过停止服务器、删除日志中的最新命令,然后重新启动 Redis 来挽救数据集。
AOF的缺点如下:
- 对于相同的数据集而言,AOF文件比RDB文件大,因此数据恢复耗时比RDB长。
3. RDB与AOF
在Redis中,支持同时开启RDB与AOF,并且也建议同时开启。
当两者同时开启时,Redis 重启时会优先使用 AOF 文件进行数据恢复,因为 AOF 包含了更完整和最新的数据。只有当 AOF 文件丢失或损坏无法恢复时,Redis 才会尝试使用 RDB 文件进行恢复。
同时开启的好处:
最大化数据安全性: AOF 确保了最少的数据丢失(例如,最多 1 秒),因为它持续记录最新的操作。
快速灾难恢复: 如果需要从零开始恢复一个庞大的数据集,使用 RDB 文件通常比回放一个巨大的 AOF 文件快得多。这在紧急情况下可以显著缩短服务恢复时间。
应对不同故障场景:
如果 AOF 文件由于某种罕见原因损坏,RDB 可以作为最后的防线。
如果 Redis 意外崩溃,AOF 确保了最新的数据。
互补优势: RDB 适合做定期全量备份和异地存储;AOF 则提供了实时的数据记录和快速恢复最新状态的能力。
参考资料
[1] Redis官网:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
[2] 尚硅谷视频教程:https://www.bilibili.com/video/BV13R4y1v7sP
[3] Docker Redis:https://hub.docker.com/_/redis
附录
Docker启动Redis
在Redis官方镜像中,是没有配置文件的,需要我们在宿主机准备配置文件目录和数据目录,并挂载到容器上:
/docker/redis/config:宿主机配置文件目录,其中包含redis.conf文件,不同版本的Redis配置文件可在官网下载:https://redis.io/docs/latest/operate/oss_and_stack/management/config/,挂载到容器`/usr/local/etc/redis`目录;/docker/redis/data:宿主机数据目录,挂载到容器/data目录;
完整启动命令如下:
bash
docker run
-v /docker/redis/config:/usr/local/etc/redis
-v /docker/redis/data:/data
-p 6379:6379
-d redis:7.4.4 redis-server /usr/local/etc/redis/redis.conf当启动完成后,会发现使用Navicat连接不上Redis,原因是Redis 默认为了安全考虑,有一些配置可能会阻止外部连接。可以修改配置文件如下配置:
bind配置:如果看到bind 127.0.0.1,这表示 Redis 只允许来自容器内部的连接,需要修改redis.conf,将其注释掉(# bind 127.0.0.1)或者改为bind 0.0.0.0,让 Redis 监听所有网络接口。protected-mode配置:如果看到protected-mode yes并且没有设置密码,这表示 Redis 拒绝来自非localhost的连接,除非客户端提供了密码。对于测试环境,可以将其改为protected-mode no。
修改配置后,重新启动容器,就可以成功连上Redis了。