持久化
Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,Redis 的持久化机制就是用来来保证 Redis 的数据不会因为故障而丢失。
Redis 的持久化机制有两种:
- 快照,快照是一次全量备份。
- AOF 日志,AOF 日志是连续的增量备份。
RDB
快照(RDB)是内存数据的二进制序列化形式,在存储上非常紧凑。
自动快照
在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb
的二进制文件中。
可以对 Redis 进行设置, 让它在 “N 秒内数据集至少有 M 个改动” 这一条件被满足时,自动保存一次数据集。
比如说,以下设置会让 Redis 在满足 “60 秒内有至少有 1000 个键被改动” 这一条件时,自动保存一次数据集:
# save 60 1000 // 关闭 RDB 只需要将所有的 save 保存策略注释掉即可
手动快照
RDB 快照还可以手动执行命令生成,进入 Redis 客户端执行命令 save
或 bgsave
可以生成 dump.rdb
文件,每次命令执行都会将所有 Redis 内存快照到一个新的 rdb 文件里,并覆盖原有 rdb 快照文件。
save
与 bgsave
对比:
命令 | save | bgsave |
---|---|---|
IO 类型 | 同步 | 异步 |
是否阻塞 Redis 其它命令 | 是 | 否 (在生成子进程执行调用 fork 函数时会有短暂阻塞) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要 fork 子进程,消耗内存 |
bgsave 的写时复制 (COW) 机制
Redis 借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常处理其他写命令。简单来说,bgsave
子进程是由主线程 fork
生成的,可以共享主线程的所有内存数据。
bgsave
子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave
子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
写时复制
fork 时全量内存拷贝是难以接受的,假设需要在一个进程中通过 fork
创建一个新的进程执行一段逻辑,fork
拷贝的大量内存空间对于子进程来说可能完全没有任何作用的,但是却引入了巨大的额外开销。
写时拷贝(Copy-on-Write)的出现就是为了解决这一问题,写时拷贝的主要作用就是将拷贝推迟到写操作真正发生时,这也就避免了大量无意义的拷贝操作。在一些早期的 unix 系统上,系统调用 fork
确实会立刻对父进程的内存空间进行复制,但是在今天的多数系统中,fork 并不会立刻触发这一过程。
在 fork
函数调用时,父进程和子进程会被 Kernel 分配到不同的虚拟内存空间中,所以在两个进程看来它们访问的是不同的内存。
在真正访问虚拟内存空间时,Kernel 会将虚拟内存映射到同一块物理内存上,所以父子进程共享了物理上的内存空间。
当父进程或者子进程对共享的内存进行修改时,共享的内存才会以页为单位进行拷贝,父进程会保留原有的物理空间,而子进程会使用拷贝后的新物理空间。
AOF 日志
RDB 快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化,将修改的每一条指令记录进文件 appendonly.aof
中 (先写入 os page cache,每隔一段时间 fsync
到磁盘)。
通过修改配置文件来打开 AOF 功能:
# appendonly yes
每当 Redis 执行一个改变数据集的命令时(比如 SET
), 这个命令就会被追加到 AOF 文件的末尾。这样的话,当 Redis 重新启动时,程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。
AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。
fsync
AOF 日志是以文件的形式存在的,当程序对 AOF 日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷到磁盘的。
这就意味着如果机器突然宕机,AOF 日志内容可能还没有来得及完全刷到磁盘中,这个时候会丢失部分数据。
为了避免这种情况,Redis 提供了三种 fsync 策略:
appendfsync always
:每次有新命令追加到 AOF 文件时就执行一次fsync
,非常慢,也非常安全。appendfsync everysec
:每秒fsync
一次,足够快,并且在故障时只会丢失 1 秒钟的数据。appendfsync no
:从不fsync
,将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync
一次, 这种 fsync
策略可以兼顾速度和安全性。
AOF 重写
AOF 文件里可能有太多没用指令,所以 AOF 会定期根据内存的最新数据生成 aof
文件。例如,执行了如下几条命令:
1 127.0.0.1:6379> incr readcount
2 (integer) 1
3 127.0.0.1:6379> incr readcount
4 (integer) 2
5 127.0.0.1:6379> incr readcount
6 (integer) 3
7 127.0.0.1:6379> incr readcount
8 (integer) 4
9 127.0.0.1:6379> incr readcount
10 (integer) 5
重写后 AOF 文件里变成:
...
5 readcount
6 $1
7 5
Redis 启动时如果既有 rdb 文件又有 aof 文件则优先选择 aof 文件恢复数据,因为一般来说 aof 中的数据更新一点。
自动重写
下面两个配置可以控制 AOF 自动重写频率:
# auto‐aof‐rewrite‐min‐size 64mb // aof 文件至少要达到 64M 才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
# auto‐aof‐rewrite‐percentage 100 // aof 文件自上一次重写后文件大小增长了 100% 则再次触发重写
手动重写
进入 Redis 客户端执行命令 bgrewriteaof
可以手动重写 AOF 日志。其原理就是开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令(比如添加一个 key,但是这时已经失效了,就不需要再添加到 AOF 日志中区),序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。
混合持久化
重启 Redis 时,一般很少使用 rdb 来恢复内存状态,因为会丢失大量数据。通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。
Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自 rdb 持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。
于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
开启混合持久化 (必须先开启 aof):
# aof‐use‐rdb‐preamble yes
如果开启了混合持久化,AOF 在重写时,不再是单纯将内存数据转换为 RESP 命令写入 AOF 文件,而是将重写这一刻之前的内存做 RDB 快照处理,并且将 RDB 快照内容和增量的 AOF(在重写时,和重写后新的命令执行)修改内存数据的命令存在一起,都写入新的 AOF 文件,新的文件一开始不叫 appendonly.aof
,等到重写完新的 AOF 文件才会进行改名,覆盖原有的 AOF 文件,完成新旧两个 AOF 文件的替换。
于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。
Redis 数据备份策略
- 写 crontab 定时调度脚本,每小时都 copy 一份 rdb 或 aof 的备份到一个目录中去,仅仅保留最近 48 小时的备份。
- 每天都保留一份当日的数据备份到一个目录中去,可以保留最近 1 个月的备份。
- 每次 copy 备份的时候,都把太旧的备份给删了。
- 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏。
- 通常 Redis 的主节点是不会进行持久化操作,持久化操作主要在从节点进行,从节点是备份节点,没有来自客户端请求的压力。但是如果出现网络分区,从节点长期连不上主节点,就会出现数据不一致的问题,所以在生产环境要做好实时监控工作,保证网络畅通或者能快速修复。