2803 字
约 9 分钟
14
S12-B07 · Redis 持久化与主从入门:RDB、AOF 与复制

重启后数据没了,第一反应是"Redis 不是会存盘吗"。它确实会,但有前提:配了哪种方式、写到什么程度、磁盘与内存够不够。默认配置下只在满足条件时落一次快照,最后一次快照之后的写入,进程一停就归零。

两件事要拆开看:数据怎么落盘决定单机丢多少,副本怎么同步决定故障时能救回多少。


1. 先分清两种"没了"

现象 真实原因 检查入口
重启后全部数据消失 没开持久化,或 dir 指到了临时目录 CONFIG GET dir、CONFIG GET save
重启后少了最近几分钟 RDB 间隔太长 / AOF 未开 CONFIG GET appendonly、INFO persistence
重启后文件在但加载失败 RDB 损坏、AOF 尾部截断 redis-check-rdb、redis-check-aof
主从切换后回退到旧数据 从库数据旧、切换前未校验 offset INFO replication 的 master_repl_offset

2. RDB:三种触发方式,一个 fork 代价

触发 方式 典型场景
规则触发 save 900 1、save 300 10、save 60 10000 日常自动快照
手动 BGSAVE(后台)/ SAVE(阻塞,别在生产用) 备份前、变更前
复制触发 从库首次全量同步时主库自动 BGSAVE 加副本、副本重建
# redis.conf 关键项(按 7.x 核对)
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
dir /var/lib/redis
rdbcompression yes
stop-writes-on-bgsave-error yes   # 落盘失败是否拒写,生产建议保持 yes

BGSAVE 靠 fork() 出子进程写盘,父进程继续服务。代价不在磁盘,在 fork 本身:fork 要复制页表,内存越大越慢;子进程写盘期间父进程的写入触发写时复制(COW),写入越密集,额外内存占用越高。典型量级是:内存越大、写入越频,抖动越明显,具体毫秒数按你的机器实测。


3. AOF:三种 appendfsync 的丢失窗口

策略 行为 最多丢 代价
always 每条命令都 fsync 几乎不丢 吞吐骤降,通常不用于高写入场景
everysec 每秒 fsync 一次(默认) 约 1 秒写入 兼顾性能与安全,绝大多数场景的默认选择
no 交给 OS 决定刷盘 可达数十秒 最快,也最不可控
appendonly yes
appendfsync everysec
appendfilename "appendonly.aof"
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes          # 混合持久化,7.x 默认

注意 Redis 7.x 的 AOF 是多文件结构(appendonlydir/ 下 base + incr 文件),不再是一个 appendonly.aof。改目录结构后,旧的备份脚本会拷错文件——这是升级到 7.x 后最常见的备份失效原因。


4. AOF 重写:文件为什么不会无限涨

AOF 记的是命令流水,INCR 一万次就是一万条。重写做的事是用当前数据状态生成一份最小命令集,把一万条 INCR 压成一条 SET。

项 说明
触发条件 增长率 ≥ auto-aof-rewrite-percentage 且 ≥ auto-aof-rewrite-min-size
谁执行 子进程重写,父进程把新写入同时追加到旧 AOF 与重写缓冲
额外开销 fork 一次 + 重写期间双份写入 + 内存中的重写缓冲
风险点 磁盘空间要能同时容纳旧文件与新文件(峰值近 2 倍)

重写期间如果磁盘被写满,stop-writes-on-bgsave-error 会让 Redis 直接拒写——表现为"服务还活着,但所有写命令报错"。


5. 混合持久化:为什么默认这么配

方案 恢复速度 丢数据窗口 文件可读性
纯 RDB 快 一个快照间隔 二进制,需工具读
纯 AOF 慢(重放全部命令) 约 1 秒 文本,可人工检查
混合(RDB 头 + AOF 尾) 快 约 1 秒 半二进制,加载最快

取舍很清楚:用 RDB 的加载速度,换 AOF 的丢数据窗口。它把"重启等几分钟"变成"几秒",代价是文件不再是纯文本。除非有审计需求,默认开即可。


6. maxmemory 与持久化:内存满了也可能救不回

情况 结果
小于数据量 + noeviction 写入报 OOM,已有数据完好,落盘正常
淘汰策略(allkeys-lru 等) 旧 key 被驱逐,落盘的是被驱逐后的数据集
设得过大,接近物理内存 BGSAVE 的 COW 峰值可能触发 OOM Killer
落盘失败 + stop-writes-on-bgsave-error yes 拒写,业务看到"连上了但不能写"

第二条最易被误判成"持久化丢数据":其实是淘汰策略先删了。对比 INFO stats 的 evicted_keys 与 INFO keyspace 的 key 数即可确认。

实用规则:至少留一份数据集大小的空闲内存给 fork 与 COW,否则快照本身成为故障源。


7. 复制的三个阶段

阶段 做什么 关键参数
全量同步 主库 BGSAVE 出 RDB,传给从库加载 repl-diskless-sync、repl-diskless-sync-delay
增量同步 从库带上 offset,主库从 backlog 补发缺失部分 repl-backlog-size(默认 1mb)
命令传播 之后主库每条写命令实时发给从库 无(持续过程)

repl_backlog 是一个环形缓冲。从库断线时间 × 写入速率 > backlog 大小时,补发失败,退化成全量同步——大实例上这等于一次网络与磁盘的双重冲击。

repl-backlog-size 64mb          # 按"最长可容忍断线时间 × 写入速率"估算
repl-diskless-sync yes          # 无盘复制,省一次落盘
repl-diskless-sync-delay 5
min-replicas-to-write 1         # 至少 1 个副本确认才允许写,防脑裂丢数据
min-replicas-max-lag 10

后两项是用可用性换一致性:副本失联超过 10 秒,主库自动拒写。小集群里很多人不开,代价是切换时可能丢一段写入。


8. 复制延迟与读写分离的一致性风险

$ redis-cli -p 6380 INFO replication
role:slave
master_link_status:up
master_last_io_seconds_ago:0
slave_repl_offset:184320
master_repl_offset:184512        # 差 192,说明有积压
风险 表现 缓解
读到自己刚写的数据失败 写完立刻查从库,查不到 关键读走主库,或写后延迟读
延迟随写入峰值放大 高峰期从库落后明显 监控两个 offset 差值并告警
从库阻塞导致积压 慢命令、落盘卡顿 关从库的 appendonly,减轻压力
全量同步反复触发 抖动 + backlog 太小 加大 backlog

读写分离不是免费的:一致性从"Redis 保证"变成"你的代码保证"。凡有"写完马上读"语义的路径,默认走主库。


9. 故障切换的三个层次与脑裂

层次 谁发现故障 谁做切换 风险
人工 人 人改配置/改 DNS 慢,但可控;适合小规模
哨兵 Sentinel 集群投票 Sentinel 提升从库 主观下线误判、双主
Cluster 集群节点 gossip 分片内主从切换 分片迁移复杂,跨 slot 操作受限

脑裂成因:主库与哨兵失联但仍接受写入,同时另一侧选出新主,两边数据各自演进;旧主回归后被强制全量同步,那段写入就没了。缓解就是第 7 节那两个 min-replicas-*:宁可拒写,也不让旧主继续收写。


10. 备份与恢复:拷贝 RDB 的正确姿势

# 1) 等快照写完再拷,别 cp 正在写的 dump.rdb
redis-cli BGSAVE && redis-cli INFO persistence | grep rdb_bgsave_in_progress

# 2) 拷贝 + 校验 + 异地
cp /var/lib/redis/dump.rdb /backup/redis-$(date +%F-%H%M).rdb
redis-check-rdb /backup/redis-$(date +%F-%H%M).rdb
scp /backup/redis-*.rdb backup-host:/data/redis/

# 3) 恢复后核对
redis-cli DBSIZE && redis-cli GET some-known-key
错误做法 后果
直接 cp dump.rdb 不等 BGSAVE 完成 拷到半个文件,恢复时报 RDB 校验失败
只备份到本机 磁盘故障时备份与数据一起没
从不演练恢复 真出事时才发现文件不可用、dir 写错、权限不对
备份不分层 误删后,最近的备份里也已被删

11. 最小示例:开 AOF、看复制

redis-cli CONFIG SET appendonly yes        # 7.x 会生成 appendonlydir/
redis-cli BGSAVE
redis-cli INFO persistence | grep -E 'rdb_last_bgsave_status|aof_last_write_status'
redis-server --port 6380 --replicaof 127.0.0.1 6379   # 一主一从
redis-cli -p 6380 INFO replication

观察四个字段:role、master_link_status(up 才算同步中)、两个 offset 的差值(延迟)、master_last_io_seconds_ago(心跳)。

# 制造延迟:往主库灌写,同时盯从库 offset
redis-cli -p 6379 --pipe < big-commands.txt
watch -n1 'redis-cli -p 6380 INFO replication | grep -E "offset|link_status"'

12. 排错:六个高频坑

现象 根因 处理
只开 RDB,丢最近数据 快照间隔内的写入无从恢复 开 AOF everysec 或缩短 save 间隔
重写期间磁盘打满 新旧文件并存,峰值近 2 倍 提前留空间,或错峰触发重写
全量同步压垮网络 backlog 太小,断线后退化 加大 repl-backlog-size,开无盘复制
升级 7.x 后备份失效 AOF 变成多文件目录 改拷 appendonlydir/ 整个目录
写命令全部报错 落盘失败 + stop-writes-on-bgsave-error 先修磁盘与权限,别急着关开关
切换后数据"倒退" 从库旧 + 无 min-replicas 保护 开 min-replicas-to-write,切换前比 offset

13. 边界:Redis 不该当唯一存储

情况 说明
数据不可重建 持久化是"降低丢失"不是"保证不丢",权威数据要有主库(MySQL 等)
需要事务与多 key 原子 持久化不解决这个,选型时就该换
强一致读 主从异步复制,天然有窗口,强一致读只能走主库
大内存实例(数十 GB) fork 与 COW 代价显著,考虑分片或关 RDB 只留 AOF
备份即合规要求 需要异地、加密、保留期策略,Redis 自带能力不够

14. 自测

  1. BGSAVE 的代价主要在哪一步?为什么内存越大越明显?
  2. appendfsync everysec 最多丢多少数据?换成 always 要付什么代价?
  3. AOF 重写的触发条件是什么?重写期间磁盘峰值为什么接近两倍?
  4. 从库断线多久之后会退化成全量同步?由哪个参数决定?
  5. 脑裂发生时,min-replicas-to-write 起什么作用?它换来的是什么代价?
  6. 为什么"直接 cp dump.rdb"是错的?正确顺序是什么?
S12-B07 · Redis 持久化与主从入门:RDB、AOF 与复制
https://www.clxhxhhr.top/posts/6091/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。