备份、恢复与重放
数据库供应商文档负责说明“怎样备份一个库”;本页定义恢复后 Wow 应用必须满足什么条件。目标不是让进程启动,而是证明以下链路仍然一致:
text
EventStore → 聚合状态/快照 → 投影与查询模型 → Saga/事件处理器先划分权威数据与派生数据
| 数据 | 角色 | 恢复要求 |
|---|---|---|
| 领域事件流 | 聚合状态的权威历史 | 必须恢复;版本顺序、事件数量和 requestId 不能丢失 |
| 快照 | 聚合加载检查点和可选查询存储 | 可从事件重建,但备份可缩短 RTO |
| 自定义投影/查询模型 | 面向读取的派生数据 | 必须能够重放、重建或通过独立备份恢复 |
| Broker 消息与消费者位点 | 尚未完成的异步工作 | 必须与 EventStore 的恢复时间点协调 |
| 补偿记录 | 失败处理和人工恢复状态 | 不能因恢复而丢失仍未完成的失败任务 |
| 唯一索引、上下文所有权和路由元数据 | 幂等、租户隔离和后端所有权 | 必须和业务数据一起恢复并验证 |
事件流是状态权威来源,但不代表其他数据可以忽略。外部副作用、补偿任务和消费者位点不能仅靠重放聚合状态自动恢复。
恢复计划必须先回答的问题
- RPO 和 RTO 分别是多少?
- 恢复点是数据库时间点、事件版本还是一次发布窗口?
- 恢复期间怎样阻止命令、消费者和定时任务写入?
- 哪些 Projection、Saga 或事件处理器会调用不可重复的外部系统?
- Broker 位点早于恢复点时怎样处理重复投递,晚于恢复点时怎样补回遗漏?
- 回滚到旧应用时,它能否读取恢复点之后已经存在的事件 revision?
没有这些答案,不要把“备份成功”写成“恢复能力已验证”。
备份流程
1. 固化清单
记录每个 bounded context 和 aggregate 使用的:
- EventStore、SnapshotStore 和查询后端;
- topic、consumer group、partition 和位点;
- tenant、owner、space 与存储路由;
requestId/聚合唯一索引;- 补偿库、上下文所有权记录和加密密钥版本;
- 应用版本、Wow 版本、配置摘要和 Schema/revision 分布。
2. 选择一致的截止点
最简单且最可靠的做法是停止命令入口和异步消费者,等待已接纳工作排空,再创建备份。必须在线备份时,应使用后端支持的时间点快照,并记录各数据库与 Broker 的实际截止点;不要假设多个系统的快照天然原子。
3. 同时保存证据
备份产物旁至少保存:
- 每个 aggregate/tenant 的事件流数量与最大版本;
- revision 和事件名分布;
- 快照数量、最大版本和更新时间;
- 投影关键行数与业务汇总;
- consumer group 位点与 lag;
- 备份校验和、工具版本和恢复命令。
只有备份文件、没有基线数据,就无法判断恢复后的缺失和重复。
隔离恢复顺序
- 创建隔离环境:禁止业务流量和外部副作用,使用独立数据库、topic 和凭据。
- 恢复 EventStore 及其辅助元数据:包括唯一索引、上下文所有权和路由所需记录。
- 验证事件流结构:逐 aggregate 检查版本连续、末版本、事件数量、revision 和反序列化。
- 启动候选应用但保持入口关闭:确认配置指向恢复副本,且不会连接生产外部系统。
- 重建或恢复快照:通过应用生成的快照重建端点或适配器支持的批处理,确保快照版本不超过 EventStore head。
- 重建投影与查询模型:每个投影必须有明确的清空、重放、幂等和断点恢复策略。
- 协调 Broker 位点:回退位点前确认所有处理器可重复;保留较新位点前证明没有事件被跳过。
- 恢复补偿任务:区分未执行、执行中、已成功和不可恢复状态,避免重复外部调用。
- 执行对账和验收:所有门禁通过后才能开放只读流量,再逐步开放命令入口。
不要直接在生产库上试验重放,也不要让恢复环境向真实支付、通知或第三方 API 发送请求。
对账矩阵
| 边界 | 至少验证 |
|---|---|
| 事件流 | aggregate/tenant 数量、版本连续性、最大版本、事件名与 revision 分布 |
| 幂等 | 同一 aggregate 的 requestId 唯一性;重试已处理请求仍被拒绝 |
| 状态 | 从版本 1..head 重放所得状态与恢复前基线或业务汇总一致 |
| 快照 | snapshot.version <= event head;抽样状态与完整重放一致 |
| 投影 | 行数、关键金额/数量、tenant 隔离、删除状态和索引查询计划 |
| Saga/处理器 | 重投不产生重复命令、重复扣款、重复通知或遗漏 |
| Broker | topic/partition、consumer group 位点、lag 和死信/重试队列 |
| 运行时 | 健康检查、追踪、指标、告警和优雅停止仍然有效 |
抽样只能发现部分错误。资金、库存、权限等高风险域需要全量业务对账。
验收请求
至少保留以下可重复证据:
- 读取一个仅靠完整事件流才能重建的聚合;
- 读取恢复的快照,并与完整重放结果比较;
- 查询一个自定义投影并核对源事件;
- 用固定
requestId重试历史命令,确认不会重复执行; - 提交一个新的测试命令,分别验证
PROCESSED、SNAPSHOT和必要的PROJECTED阶段; - 重启恢复环境,再次验证状态和消费者位点。
回滚门禁
- 原始备份保持只读,不覆盖最后一个已知良好的恢复点;
- 恢复演练产生的新写入与生产命名空间隔离;
- 记录应用、配置、事件 Upgrader 和数据库 Schema 的组合版本;
- 回滚前确认旧应用可以读取当前事件 revision;
- 如果开放流量后才发现问题,先再次停流并固化增量写入,再决定前滚修复或回滚。
“把数据库恢复回去”不是完整回滚:Broker 位点、外部副作用和恢复后新增事件也必须处理。
演练频率与完成标志
定期在空白环境从备份开始演练,而不是在已有测试库上覆盖恢复。完成记录应包含耗时、数据量、校验结果、未覆盖边界和责任人。只有实际恢复时间满足 RTO、数据损失满足 RPO、全量高风险对账通过,才能把恢复门禁标记为完成。