REDIS 7 · RDB · AOF · FORK · COPY-ON-WRITE · FSYNC
内存一断电就清零,所以要么拍照,要么记账
Redis 的数据都在内存里,进程一退出就没了。持久化就是把它存到磁盘上,重启时再读回来。有两种办法:隔一段时间给整个内存拍张照(RDB),或者把每一条写命令记进账本(AOF)。
RDB:拍照dump.rdb
某一刻整个内存长什么样,压缩成一个二进制文件。文件小、恢复快,但两张照片之间写入的数据,宕机就丢了。
AOF:记账appendonly.aof
每执行一条写命令,就在文件末尾追加一行。重启时从头再执行一遍。丢得少,但文件大、恢复慢。
白色竖线是 RDB 拍照的时刻(示意:每 15 分钟一次);AOF 按默认的每秒刷盘。绿色是重启后还在的数据,红色是丢掉的。
redis-server用一个线程处理所有命令,它一卡,所有客户端都卡。
持久化尽量别让它干重活fork()主进程复制出来的分身,替它把内存写到磁盘。
写 RDB、重写 AOF 都靠它dump.rdb某一刻的全量数据,紧凑的二进制文件。
适合备份、恢复快appendonly.aof每条写命令按顺序追加,重启时从头重放。
丢得少,越记越长RDB:怎么拍一张不卡的照片
直接让主进程去写文件(SAVE 命令)会把它卡住几十秒,生产环境不能用。平时用的是 BGSAVE:fork 一个子进程去写,主进程接着处理请求。
save 规则到了、手动 BGSAVE、从节点要全量同步(正常关机时是直接 SAVE)temp-<pid>.rdb,压缩、刷盘dump.rdb,旧照片这时才被替换fork 只复制页表,不复制数据,所以很快。灰色表示两边指向同一块物理内存。
这叫写时复制(Copy-On-Write)。子进程看到的永远是 fork 那一刻的样子。写得越多,复制的页越多,最坏情况内存接近翻倍。
save 3600 1 300 100 60 10000
# 3600 秒内至少 1 次修改,或
# 300 秒内至少 100 次修改,或
# 60 秒内至少 10000 次修改,就 BGSAVE 一次
save "" # 关掉 RDB
stop-writes-on-bgsave-error yes # 拍照失败就拒绝写入
好处:一个紧凑的文件,拷走就是一份完整备份;重启时直接加载,比重放命令快得多;平时写盘的活都在子进程,主进程几乎不受影响。
代价:两次拍照之间的写入,宕机就丢,通常是几分钟;数据量大时 fork 会让主进程停顿,写多时写时复制会多占内存。
AOF:每一笔都记账
打开 appendonly yes 后,每条写命令执行完,会先放进内存里的缓冲区,再写进文件。写进文件还不等于落到磁盘:操作系统会先放在自己的缓存里,要等 fsync 才真正写到盘上。
always每条都刷盘
- 断电丢
- 几乎不丢
- 性能
- 最慢,每次写都等磁盘
everysec默认,每秒刷一次
- 断电丢
- 约 1 秒(极端情况 2 秒)
- 性能
- 好,后台线程去刷
no交给操作系统
- 断电丢
- Linux 通常最多约 30 秒
- 性能
- 最快
innodb_flush_log_at_trx_commit 是同一个取舍:设成 1 相当于 always,设成 2 相当于 everysec(进程挂了不丢,断电丢约 1 秒)(数据库第 01 篇)。不同的是,MySQL 先写日志再改数据,Redis 先执行命令再写日志。账本越记越长:AOF 重写
同一个 key 改了一万次,账本里就有一万行,其实只需要最后一行。文件比上次重写后大一倍、并且超过 64 MB 时(默认),Redis 会 fork 子进程重写一份最短的账本。
- SET stock 100
- DECR stock
- DECR stock
- DECR stock
- SADD tags a
- SADD tags b
- SREM tags a
- SET tmp x
- DEL tmp
- SET stock 97
- SADD tags b
子进程不读旧文件,而是直接看内存里现在是什么,写出能还原它的最少命令。
宕机实验台:选一种配置,拖动宕机时刻
Redis 每秒写入 100 次,跑了 5 分钟。选持久化方式和宕机方式,拖动红线,看重启后能恢复到哪一刻、丢了多少。
示意模型:RDB 按 save 60 1000 规则大约每分钟拍一次,每次子进程写 8 秒;混合持久化在第 150 秒做过一次 AOF 重写;断电时 everysec 按丢 1 秒、no 按丢 30 秒算(最坏情况)。
混合持久化:照片打底,账本补尾巴
AOF 重写时,子进程不再写一堆命令,而是直接写一张 RDB 照片当开头,之后的写命令再追加在后面。重启时先加载照片(快),再重放后面那一小段命令(少)。Redis 5 起默认开启(aof-use-rdb-preamble yes),前提是打开了 AOF。
appendonlydir/
├── appendonly.aof.1.base.rdb # 上次重写时的照片(RDB 格式)
├── appendonly.aof.1.incr.aof # 之后追加的写命令
└── appendonly.aof.manifest # 清单:现在用的是哪几个文件
开了 AOF 就只认 AOF,即使旁边还有一个更新的 dump.rdb 也不会读。
| 不持久化 | 只开 RDB | AOF everysec | AOF always | 混合(推荐) | |
|---|---|---|---|---|---|
| 断电丢多少 | 全部 | 几分钟 | 约 1 秒 | 几乎不丢 | 约 1 秒 |
| 进程被 kill 丢多少 | 全部 | 几分钟 | 几乎不丢 | 几乎不丢 | 几乎不丢 |
| 文件大小 | — | 小 | 大 | 大 | 中 |
| 重启速度 | 最快(空的) | 快 | 慢,要逐条重放 | 慢 | 快 |
| 平时开销 | 没有 | 定期 fork | 每秒刷盘 + 重写时 fork | 每条写都等磁盘 | 同 everysec |
| 适合 | 纯缓存,数据库里都有 | 能接受丢几分钟,要做备份 | 要少丢数据 | 极少用 | 大多数需要持久化的场景 |
怎么选
先问一句:Redis 里的数据丢了,能不能从别处补回来?
重启后空着,按需从库里加载。注意预热和击穿(Redis 第 01 篇)。想重启后快点热起来,可以只开 RDB。
比如排行榜、统计数据。RDB 文件拷到别的机器,就是一份备份。
最多丢一秒左右,重启也快。官方的建议也是 RDB 和 AOF 一起开。
就算 always,主从复制也是异步的,主节点挂了切到从节点,没同步过去的写入一样会丢。
save 3600 1 300 100 60 10000 # RDB 规则(Redis 7 默认)
appendonly yes # 默认 no,要手动打开
appendfsync everysec # always / everysec / no
aof-use-rdb-preamble yes # 混合持久化
auto-aof-rewrite-percentage 100 # 比上次重写后大一倍就重写
auto-aof-rewrite-min-size 64mb # 小于这个大小不重写
aof-load-truncated yes # 文件末尾不完整时,截掉最后一条后照常启动
Redis 6 和 Redis 7
Java 这边不用改:持久化是 Redis 服务端的事,Boot 2 还是 Boot 3、JDK 8 还是 17 都一样。版本差异在 Redis 自己身上。
Redis 6.x 及以前
单文件 AOF- AOF 就是一个
appendonly.aof文件 - 重写期间,主进程把新写入同时攒在一个重写缓冲区里,重写结束时再追加到新文件,多占内存,收尾时还会卡一下
- 默认 RDB 规则是
save 900 1 / 300 10 / 60 10000
Redis 7.0 起
多文件 AOF- AOF 拆成 base + incr + 清单,放在
appendonlydir目录 - 重写时主进程直接开一个新的 incr 文件接着写,不再需要重写缓冲区
- 默认 RDB 规则改成
save 3600 1 300 100 60 10000 - 可以给 AOF 打时间戳(
aof-timestamp-enabled),配合redis-check-aof恢复到某个时间点
MISCONF 字样的异常。常踩的坑
持久化的坑,一半是 fork 带来的,一半是以为“开了就不会丢”。
后台拍照失败(磁盘满了、目录没权限),Redis 默认拒绝一切写入。先修磁盘,再看日志里 BGSAVE 失败的原因。
fork 时主进程停住,实例越大停得越久。INFO stats 里的 latest_fork_usec 能看到上次停了多久。单个实例别太大,内存大就拆成集群。
写得多时,写时复制让内存涨很多。maxmemory 要留出余量;启动日志里关于 overcommit 的警告要处理,把 vm.overcommit_memory 设成 1,否则 fork 可能直接失败。
透明大页让每次写时复制要复制 2 MB 而不是 4 KB。Redis 启动时检测到它开着会打警告,按提示关掉(新版本 Redis 也会尝试只对自己关掉)。
主节点没开持久化又设置了自动重启:它空着启动,从节点跟它同步,于是也被清空。主节点要么开持久化,要么别自动重启。
只开 AOF、从没重写过,要把几千万条命令重放一遍。打开自动重写和混合持久化。
磁盘太慢,每秒一次的刷盘跟不上,Redis 开始拖慢写入。换更快的盘,或者别和其他重 IO 的程序共用一块盘。
开了 AOF 且还没重写的话,马上停止 AOF 重写、停掉 Redis,把 AOF 末尾那条 FLUSHALL 删掉再启动,就能找回来。一旦重写了就没了,所以要靠 RDB 备份。
末尾只写了半条命令时,默认 aof-load-truncated yes 会截掉它照常启动;坏在中间就要用 redis-check-aof --fix 修,修之前先备份。
dump.rdb 只在本机,盘坏了一起没。定期把 RDB 拷到别的机器或对象存储,并且真的试着恢复一次。