读完这篇,你能做什么
能说明不同故障下可能丢失哪些数据,并制定包含 TTL 的恢复核对步骤。
学习理由、准备要求与知识点
为什么学它
主从与持久化都不是零丢失承诺;恢复演练决定备份是否真的能支撑业务。
先读这些内容
知识点清单
- RDB 快照
- AOF 追加
- fsync 策略
- 恢复顺序
- 异步复制
- 复制延迟
- 故障切换
- 隔离恢复校验
运行前先确认Redis 7.4;Java 客户端源码固定 Jedis 5.2.0。完整 Java 17 程序模拟快照与日志的恢复语义,不读取或生成 Redis RDB/AOF 格式。最新文档中的新增备份命令不自动适用于 7.4。
01先弄懂它是什么
讨论 Redis 恢复前,先回答业务问题:最多允许丢多少已接受的数据,多久要恢复服务,哪些内容能从数据库重建?商户缓存可以重新加载,登录态可以让用户重新登录,但尚未保存在别处的订单事件可能再也补不回。不能把所有 Redis 数据都当成随时可以丢弃的缓存。
RDB 保存某个时间点的数据状态,AOF 记录可用于重建数据的写入历史,复制则把主节点变化发给副本。它们应对的问题不同:副本能接管服务,但误删也可能被同步过去;同一主机上的备份挡不住整台主机或磁盘丢失。看到文件存在、复制连接正常,都还不能证明恢复后的数据正确,需要实际恢复检查。
恢复数据时,还要记住它原本什么时候到期。例如登录态原定一小时后失效,停机两小时再恢复,就不应重新给它一小时。TTL 只是“查询那一刻还剩多久”;恢复快照和日志时,需要考虑原来定下的到期时刻。验收时也不能只数键的数量,还要抽样检查值、类型和有效期,并核对业务上不能重复的记录与重要计数。
文中这几个词,先认一认
- RPO
- 允许丢失多长时间内的数据变更,例如最多接受恢复到故障前某个时间点。
- RTO
- 发生故障后,允许花多长时间恢复服务,包括发现、处理和验证的时间。
- 复制确认
- 副本报告已处理相应写入,但这不自动保证任何故障下都不会丢数据。
02看看它怎么工作
先在独立练习实例读取 INFO persistence、INFO replication,以及有权限时的 CONFIG GET appendonly、CONFIG GET appendfsync 和 CONFIG GET save。
记录实际角色、持久化策略及最近错误,再按对应版本方法保存完整备份。Redis 7 的 AOF 可以由基文件、增量文件及清单组成,不能只凭旧教程复制一个同名文件就推断备份完整;目录和文件集合应以当前配置与版本说明为准。
主节点通常不会等副本全部跟上才完成写入,这就是异步复制。WAIT 可以等待“当前连接之前的写入”获得指定数量的副本确认;等到超时,也会返回实际确认了多少个,应用要检查这个数。确认不够,不表示原写入已撤销。Redis 7.2 起提供的 WAITAOF 还可等待 AOF 刷盘确认,但仍要核对由哪些节点确认、配置怎样设置,以及故障切换会选谁。若通信中断导致结果未知,不能直接再做一次扣减。
一份可信的恢复记录至少区分备份时间点、恢复启动时间与验证完成时间。恢复到隔离实例后,先禁用真实消费者入口,核对关键数据与过期状态,再讨论流量切换。下面的教学程序将快照序号、连续日志和绝对过期时间组合起来;它故意拒绝缺口与未达清单终点的输入,让恢复缺失成为可见失败,而不是悄悄宣布成功。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
A["定义数据重要性 RPO RTO"] --> B["记录持久化与复制状态"]
B --> C["保存完整备份及校验清单"]
C --> D["恢复到隔离实例"]
D --> E["验证值 类型 TTL 与业务不变量"]
E --> F["评估切换和后续对账"]按顺序看,每一步做了什么
- 列出可以重建、可以失效和必须恢复的数据。
- 记录当前版本、角色、持久化参数和备份文件集合。
- 在不同故障域保存备份与必要元信息,验证完整性。
- 用隔离恢复测量 RPO
/RTO,并核对过期与业务不变量。
03对照实现代码
Jedis v5.2.0 · Jedis#waitReplicas(int,long);Jedis#waitAOF(long,long,long)
以下是原创客户端调用契约模型,强调同一连接与确认不足的结果;不是 Jedis 源码节选,也不是 Redis 持久化引擎实现。Wire 接口没有网络实现。
class SourceModel {
interface Wire {
void set(String key, String value);
long waitReplicas(int replicas, long timeoutMillis);
}
enum Result { CONFIRMED_THRESHOLD, WRITTEN_BUT_THRESHOLD_NOT_MET }
static Result write(Wire sameConnection, String key, String value) {
sameConnection.set(key, value); // 写入和等待必须属于同一连接的上下文
long acknowledged = sameConnection.waitReplicas(1, 500);
if (acknowledged >= 1) return Result.CONFIRMED_THRESHOLD; // 检查返回值而非只看无异常
return Result.WRITTEN_BUT_THRESHOLD_NOT_MET; // 不足不会撤销已发送写入
}
}真实 waitReplicas 先检查当前调用模式,再通过持有的 connection 发送 WAIT,随后读取整数响应。Java 客户端没有在此替服务端完成磁盘刷写。
waitAOF 发送 WAITAOF 并把响应转换为两个计数,不能将其误读为一个无需检查的成功布尔值。
相关确认针对同一连接上的写入,因此写入和确认要使用正确关联的连接。若每一步都各自向连接池借用,可能已经换成不同连接,应采用客户端提供的同一连接使用方式。
模型仅返回已知响应的两种情况;连接异常应由调用者保留为结果未知,并使用稳定业务标识查询或对账,不能转成确定失败后盲重试。
代码先调用谁,再调用谁
- 业务写入使用固定连接
Jedis.waitReplicasconnection.sendCommand(WAIT)connection.getIntegerReply- 业务检查确认数量
04动手写一个例子
带清单终点与绝对有效期的快照日志恢复
从序号 base 的快照开始,严格按连续序号重放 SET/DEL 记录,要求最终到达备份清单 last。恢复时刻 now 已过期的键应消失,不能重新开始 TTL。此格式为自定义教学事件,不是 Redis AOF。
- 输入 1
快照k=old;日志SET k=new,到期90;恢复时刻100- 输出 1
k不存在,旧值也不能复活
- 输入 2
base=5,last=7;只提供序号6- 输出 2
拒绝:日志尾部缺失
先复制快照到临时状态,确保任何验证失败都不会修改调用者提供的快照。
逐条验证下一序号恰好加一,再覆盖值或删除键;已经在恢复时刻过期的 SET 也要参与覆盖,最后统一过滤。
重放终点必须匹配清单。清单只声明备份自身覆盖范围,不能证明故障前未进入备份的所有写入都已保存。
为什么这样写是对的处理到序号 s 时,临时状态精确表示快照与 base+1 到 s 的有序变更;对外返回前,所有有限到期点都严格晚于 now。
复制 k 个快照键,处理 n 条记录并遍历最终键集;哈希表平均单次更新为常数,字符串内容存储成本另计。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.util.*;
public class Main {
record Value(String text, long expiresAt) {
Value {
Objects.requireNonNull(text);
if (expiresAt < -1) throw new IllegalArgumentException("expiry");
}
}
record Event(long sequence, String key, Value value) {} // null 值明确表示删除事件
static Map<String, Value> restore(long base, long last, Map<String, Value> snapshot,
List<Event> events, long now) {
if (base < 0 || last < base || now < 0) throw new IllegalArgumentException("range");
Map<String, Value> state = new HashMap<>(snapshot); // 验证失败不污染输入快照
long current = base;
for (Event event : events) {
Objects.requireNonNull(event.key());
if (event.sequence() != Math.addExact(current, 1))
throw new IllegalArgumentException("gap or duplicate");
current = event.sequence();
if (event.value() == null) state.remove(event.key());
else state.put(event.key(), event.value()); // 先覆盖旧值,不能跳过已到期的新值
}
if (current != last) throw new IllegalArgumentException("manifest mismatch");
state.values().removeIf(v -> v.expiresAt() != -1 && v.expiresAt() <= now);
return Map.copyOf(state); // 返回恢复时仍有效的独立状态
}
static Value forever(String s) { return new Value(s, -1); }
static void check(boolean ok) { if (!ok) throw new AssertionError(); }
static void bad(Runnable action) {
try { action.run(); } catch (IllegalArgumentException expected) { return; }
throw new AssertionError("expected rejection");
}
public static void main(String[] args) {
Map<String, Value> base = Map.of("k", forever("old"));
check(restore(5,5,base,List.of(),100).get("k").text().equals("old"));
check(restore(5,6,base,List.of(new Event(6,"k",new Value("new",90))),100).isEmpty());
check(restore(5,6,base,List.of(new Event(6,"k",null)),100).isEmpty());
check(restore(5,6,base,List.of(new Event(6,"x",new Value("live",101))),100).size()==2);
check(restore(5,6,base,List.of(new Event(6,"k",new Value("edge",100))),100).isEmpty());
bad(() -> restore(5,7,base,List.of(new Event(7,"x",forever("v"))),100));
bad(() -> restore(5,7,base,List.of(new Event(6,"x",forever("v"))),100));
check(base.get("k").text().equals("old")); // 重放成功或失败都不改变原始快照
System.out.println("8 recovery checks passed");
}
}这些特殊情况也要试一试
expiresAt=-1 表示永久键,到期点等于恢复时刻则视为过期。- 日志重复、缺口与尾部缺失都拒绝,实际格式还需校验损坏与事务边界。
- 恢复时钟不准确会改变到期判断;真实环境需要可靠时间来源。
进阶选读 · 生产拓展:大规模恢复的校验与断点
场景数据量大,完整恢复需要数小时,希望提前发现损坏并在中断后继续。
做法用带格式版本的清单写明备份分成哪些块、每块覆盖哪些序号,以及用于检查内容的摘要。每块校验通过后,记录一个只读的已完成位置,便于中断后继续。全部恢复后,再按同一个目标时间点核对业务数据。哈希摘要相符,只能说明内容与清单一致;清单本身也必须可靠保存。
复杂度顺序读写与摘要计算为 O(B),B 为备份字节数;额外内存可按分块大小限制。
好处与代价断点降低重做成本,但格式升级、过期语义和跨分片业务一致性必须纳入协议。
05项目里怎么用
备份除了数据,还要保留配置、版本和完整文件集合,并放在不会随原实例一起失效的位置。everysec 描述刷盘策略,不是对所有硬件和系统故障都保证最多丢一秒;操作系统缓存、存储损坏和副本切换都可能影响结果。不能接受丢失的业务事实,还应评估更合适的存储与确认方式。
恢复演练先创建永久键、Hash 和短 TTL 键,保存备份后再更改其中一部分,恢复到另一个实例核对时间点差异。明确恢复端口、网络隔离和消费者配置,再记录加载时间、键值摘要、剩余 TTL 及业务查询结果。只验证 PING 成功说明服务可访问,不能证明数据符合预期。
AOF 重写、生成快照和复制全量同步会竞争 CPU、内存与 I/O,应预留容量并监控失败状态。发现文件异常时先保留副本和证据,再按版本工具与恢复策略处理;直接修复唯一文件会减少回退选择。恢复完成后仍需对账补偿备份窗口内未覆盖的业务变更。
容易踩的坑
- 将副本当成能自动回到误删前的历史备份。
- WAIT 返回不足就认为写入已回滚。
- 恢复时把原剩余 TTL 重新完整设置,延长旧会话寿命。
- 只拷贝一个 AOF 文件,未核对版本对应的清单与文件集合。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| RDB 时间点备份 | 便于保留历史状态和搬运 | 快照之间的变更可能不在恢复范围 | 可接受明确数据窗口的备份 |
| AOF 与合适刷盘策略 | 可缩小部分故障的数据损失窗口 | 增加写入和恢复管理成本 | 需要较细恢复粒度的状态 |
| 复制加独立备份 | 同时考虑可用性与历史恢复 | 需要多个故障域和定期演练 | 承担重要业务状态的部署 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01复制为什么不能替代备份?
展开参考答案与推导
复制通常传播当前变更,误删也会传播;独立历史备份保留先前状态。
02WAIT 确认的是哪一批写入?
展开参考答案与推导
当前连接此前的写入;需要检查实际副本确认数,不能把连接池中别的连接写入混为同一上下文。
03RPO 与 RTO 有什么区别?
展开参考答案与推导
前者描述可接受的数据损失时间窗口,后者描述可接受的服务恢复时长。
换个情况,还会处理吗
01恢复后的短期登录态重新获得完整 TTL,有什么问题?
展开参考答案与推导
恢复延长了原凭证有效期,可能重新启用本应失效的会话;应按绝对到期语义验证。
02WAIT 超时返回零,能再做一次 INCR 吗?
展开参考答案与推导
不能据此判断原 INCR 未执行。应通过业务幂等标识或对账确定结果,避免重复增加。
03备份可以加载但订单计数不一致,算恢复成功吗?
展开参考答案与推导
只能说明格式可读,业务不变量尚未满足;需定位恢复窗口与缺失数据后再决定切换。
写代码试一试
01旧永久值之后被一个已过期的新值覆盖,恢复保留哪个?
- 输入 / 条件
old永久;SET new到期90;now100- 输出 / 预期
键不存在
提示 不能先跳过过期事件。
展开参考答案与推导
先应用覆盖再过滤过期,否则错误保留 old,形成数据复活。
02base为8,日志首条为10,如何处理?
- 输入 / 条件
8→10- 输出 / 预期
拒绝缺口
提示 预期下一序号为9。
展开参考答案与推导
程序验证连续性,缺少9时不能知道遗漏操作对状态的影响。
03清单last为9而日志只到8,是否返回部分状态?
- 输入 / 条件
预期9,实际8- 输出 / 预期
拒绝清单不匹配
提示 区分部分恢复与完整恢复。
展开参考答案与推导
本题契约只返回完整结果,部分恢复应有独立状态和显式审批流程,不能默认为成功。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能说明不同故障下可能丢失哪些数据,并制定包含 TTL 的恢复核对步骤。
本篇重点加练
持久化策略只能形成恢复假设,只有隔离恢复和业务校验才能证明材料实际可用。
- fsync 策略
- 隔离恢复校验
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
本地实例先保存一批商品缓存,再连续修改少量键。比较 RDB 和 AOF 中恢复材料表达的内容,并列出判断材料是否完整所需信息。
- 提交内容
- 恢复材料清单
- 怎样算完成
- 区分快照时点与操作序列
- 包含配置及版本
练到的知识点RDB 快照 · AOF 追加
- 作业 02代码推演
AOF 有两种 fsync 配置,实例在接收写入、写入文件缓冲和持久化后分别退出。画出可能丢失窗口,并分析同时存在 RDB
/AOF时要核查的启动配置。- 提交内容
- 故障时间线与核查表
- 怎样算完成
- 不把返回成功等同于落盘
- 注明实际恢复使用材料
练到的知识点fsync 策略 · 恢复顺序
- 作业 03原理问答
主机掉电、进程退出和客户端断连对写入证据的影响不同。建立 fsync 时点与这三类故障的关系,列出需要注明的操作系统假设。
- 提交内容
- 故障与证据矩阵
- 怎样算完成
- 区分内存、缓冲与稳定存储
- 不混淆传输确认和持久化
练到的知识点fsync 策略
综合应用
- 作业 04面试场景
副本落后主节点一段时间,运维仅看到连接仍在线。定义复制延迟的观测口径及不同业务读副本时的容忍条件。
- 提交内容
- 指标字典与业务分级
- 怎样算完成
- 区分连接状态和数据进度
- 包含写后立即读场景
练到的知识点异步复制 · 复制延迟
- 作业 05设计评审
需要验证一次故障切换后的业务数据,手头有备份和最近操作日志。制定隔离恢复过程,说明如何证明恢复结果达到目标。
- 提交内容
- 恢复检查单与验收口径
- 怎样算完成
- 不覆盖现有实例
- 检查关键数据和 TTL 而非只看启动成功
练到的知识点故障切换 · 隔离恢复校验
- 作业 06排错实验
用独立本地 Redis 实例生成小备份,复制到新建恢复目录启动另一实例;对预先记录的键、类型、值和有效期进行校验。
- 提交内容
- 命令、清单与恢复报告
- 怎样算完成
- 源实例和目标端口清楚
- 注明实验未验证的真实故障与复制场景
练到的知识点RDB 快照 · AOF 追加 · fsync 策略 · 恢复顺序 · 异步复制 · 复制延迟 · 故障切换 · 隔离恢复校验
- 作业 07设计评审
备份文件可加载但来自不同时刻的配置、应用版本与数据。制定恢复材料的配套清单,说明业务校验应覆盖哪些格式假设。
- 提交内容
- 恢复清单与校验项目
- 怎样算完成
- 包含 TTL 时间口径
- 材料版本能够相互对应
练到的知识点隔离恢复校验
- 作业 08排错实验
在本地生成两个小型备份副本,模拟清单缺项或文件截断,只在新建临时恢复实例验证;记录失败阶段与可用诊断信息。
- 提交内容
- 实验记录与材料校验器
- 怎样算完成
- 不损坏原始备份
- 无法模拟掉电的范围明确标注
练到的知识点fsync 策略 · 隔离恢复校验
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
业务要求最大丢失一分钟数据、十分钟恢复服务。估算备份大小、传输、加载和校验耗时,并制定可重复演练计划。
- 提交内容
- 预算表与演练记录模板
- 怎样算完成
- 用测量支持时间预算
- 区分目标承诺和已验证能力
练到的知识点RDB 快照 · fsync 策略 · 异步复制 · 故障切换 · 隔离恢复校验
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 条件变化
如果恢复目标是“恢复到指定分钟之前的业务状态”,评审现有 RDB
/AOF材料是否包含足够时间与业务边界信息。- 提交内容
- 可恢复性研究提案
- 怎样算完成
- 区分加载某状态和时间点恢复
- 列出缺失信息而不编造能力
练到的知识点fsync 策略 · 隔离恢复校验
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读实践指南英文
Redis:Persistence (新标签页)↗
比较 RDB、AOF 与恢复过程,按可接受的数据丢失窗口设计备份验证。
- 实践参考文档英文
Redis:Replication (新标签页)↗
阅读异步复制、故障切换与 WAIT 的限制,避免把副本等同于备份。
- 深入参考文档英文
Redis:EXPIRE (新标签页)↗
阅读 TTL、覆盖写入和更新命令的交互,检查缓存是否会意外永不过期。
- 深入参考文档英文
Redis WAIT (新标签页)↗
核对连接范围与返回确认数量。
- 深入参考文档英文
Redis WAITAOF (新标签页)↗
比较复制处理确认与 AOF 刷盘确认。
- 深入参考文档英文
Jedis 5.2.0 源码 (新标签页)↗
查看 WAIT 和 WAITAOF 命令的 Java 客户端封装。