备份、恢复与事件响应
正在读取学习记录…
资料助手保存了五条业务笔记。12分备份时只有前三条,16分和19分又分别写入第四、第五条。20分服务故障,用户关心两件事:什么时候可以重新使用,刚刚保存的内容是否还在。
备份是为恢复保留数据副本;恢复是把副本及后续变更装入可用系统并核对。事件响应是故障期间的协作过程,包括识别、止损、调查、恢复、沟通和复盘。正常恢复流程先暂停写入,选择可信快照,重放后续变更,验证数据与权限,最后恢复流量。
时间目标和数据目标怎样分别描述
RTO(恢复时间目标)表示允许服务中断多久;RPO(恢复点目标)表示最多允许丢失多长时间内的数据。假设业务要求RTO不超过10分钟、RPO不超过5分钟:20分发生故障、26分恢复,演练时间线给出6分钟中断;只恢复12分快照,会失去16分和19分两笔已确认写入,按故障时刻算恢复点落后8分钟。
图中快照覆盖1–3,日志保存4、5。恢复时先复制快照,再按ID补齐日志。19分之后没有新确认写入;完整日志恢复后,已确认写入没有丢失。代码另报“已确认写入覆盖缺口”,以最后确认写入19分减去恢复水位计算:只用快照为7分钟,完整重放为0。这个值与故障时刻到快照的8分钟采用不同参照点,要分别解读。
| 参照 | 快照恢复 | 快照加完整日志 |
|---|---|---|
| 数据条数 | 3 | 5 |
| 最新覆盖写入时刻 | 12分 | 19分 |
| 相对19分最后确认写入的缺口 | 7分钟 | 0分钟 |
| 演练恢复完成时刻 | 26分 | 26分 |
| 20到26分的中断时间 | 6分钟 | 6分钟 |
这条时间线是固定演练输入,SQLite复制和核对会真实执行;6分钟表示场景中的恢复过程,程序没有真实等待6分钟或测量生产RTO。
从独立副本恢复五条笔记
建立新文件夹 l8-incident,保存 restore_notes.py 和 test_restore_notes.py。Python SQLite备份接口将一个数据库复制到另一连接。示例先创建活动库,再创建快照,关闭原连接,最后在新的数据库中恢复,避免“查原库确认数据还在”掩盖恢复问题。
日志为每次确认写入保留ID、内容与逻辑分钟。主键用于去重;重复记录且内容相同可以跳过,ID相同而内容不同必须报告冲突。最后把恢复结果与所有已确认内容比较,缺少一笔也不能宣布完整。
先在 PowerShell 创建并进入目录,再保存下方两份文件:
New-Item -ItemType Directory -Force l8-incident
cd l8-incidentmacOS 终端:
mkdir -p l8-incident
cd l8-incident"""执行SQLite快照恢复和日志重放;时间线使用固定演练分钟。"""
import sqlite3
REPLAY_JOURNAL = True
JOURNAL = [(4, "午餐报销", 16), (5, "出差申请", 19)]
def seed():
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL)")
db.executemany("INSERT INTO notes VALUES (?, ?)",
[(1, "入职材料"), (2, "交通报销"), (3, "休假流程")])
db.commit()
return db
def recover(replay=True, journal=JOURNAL):
live = seed()
snapshot = sqlite3.connect(":memory:")
# 12分做快照,后续写入不会出现在该副本中。
live.backup(snapshot)
live.executemany("INSERT INTO notes VALUES (?, ?)", [(i, text) for i, text, _ in JOURNAL])
live.commit()
expected = live.execute("SELECT id, text FROM notes ORDER BY id").fetchall()
live.close() # 演练中放弃原连接,恢复到独立数据库。
restored = sqlite3.connect(":memory:")
snapshot.backup(restored)
watermark = 12
if replay:
for note_id, text, minute in journal:
# 相同id、相同内容可重放;冲突内容必须停下来调查。
old = restored.execute("SELECT text FROM notes WHERE id=?", (note_id,)).fetchone()
if old and old[0] != text:
restored.close()
snapshot.close()
raise ValueError("重放冲突")
restored.execute("INSERT OR IGNORE INTO notes VALUES (?, ?)", (note_id, text))
watermark = max(watermark, minute)
restored.commit()
actual = restored.execute("SELECT id, text FROM notes ORDER BY id").fetchall()
restored.close()
snapshot.close()
# 故障20分,恢复26分;19分为最后一条已确认写入。
return actual, actual == expected, 19 - watermark, 26 - 20
def main():
notes, complete, loss_minutes, restore_minutes = recover(REPLAY_JOURNAL)
print("12分: 快照3条 / 16分: 写入4 / 19分: 写入5")
print("20分: 故障,暂停写入 / 26分: 恢复校验")
print("恢复ID:", [note_id for note_id, _ in notes])
print("内容核对:", "一致" if complete else "缺少已确认写入")
print(f"已确认写入覆盖缺口: {loss_minutes}分钟 / 演练恢复时间: {restore_minutes}分钟")
if __name__ == "__main__":
main()测试文件 test_restore_notes.py:
"""核对恢复内容、快照缺口、重复重放和冲突,不依赖原库。"""
import unittest
from restore_notes import JOURNAL, recover
class RestoreTests(unittest.TestCase):
def test_complete_restore(self):
notes, complete, loss, elapsed = recover()
self.assertEqual([i for i, _ in notes], [1, 2, 3, 4, 5])
self.assertTrue(complete)
self.assertEqual((loss, elapsed), (0, 6))
def test_snapshot_alone(self):
notes, complete, loss, _ = recover(False)
self.assertEqual(len(notes), 3)
self.assertFalse(complete)
self.assertEqual(loss, 7)
def test_duplicate_journal(self):
self.assertTrue(recover(journal=JOURNAL + JOURNAL)[1])
def test_conflicting_write(self):
with self.assertRaisesRegex(ValueError, "重放冲突"):
recover(journal=JOURNAL + [(4, "冲突内容", 20)])
def test_incomplete_journal_detected(self):
self.assertFalse(recover(journal=JOURNAL[:1])[1])
if __name__ == "__main__":
unittest.main()文件保存后,在同一窗口核对文件名并运行:
ls
python -X utf8 restore_notes.py
python -X utf8 -m unittest -v test_restore_notes.pydir应显示这两个文件。运行程序后得到下面输出;测试末尾应显示 OK。
程序输出:
12分: 快照3条 / 16分: 写入4 / 19分: 写入5
20分: 故障,暂停写入 / 26分: 恢复校验
恢复ID: [1, 2, 3, 4, 5]
内容核对: 一致
已确认写入覆盖缺口: 0分钟 / 演练恢复时间: 6分钟只使用快照会丢掉什么
在 restore_notes.py 把 REPLAY_JOURNAL = True 改成 REPLAY_JOURNAL = False,运行:
python -X utf8 restore_notes.py恢复ID变成 [1, 2, 3],内容核对变成“缺少已确认写入”,覆盖缺口变成7分钟,演练恢复时间仍为6分钟。恢复 REPLAY_JOURNAL = True 并再次运行,得到五条笔记和0分钟缺口。
测试还重放重复日志、冲突日志和缺少第五条的日志。重复不增加条数,冲突触发错误,不完整日志导致核对失败。真实恢复还要确认日志的持久化、连续性和提交边界;单靠最大时间戳无法证明中间没有缺失记录。
故障时怎样分工并逐步恢复
Google SRE 的事件管理章节介绍了集中协调和清晰分工。事件指挥负责决定优先级与控制变更,技术处理者执行止损和恢复,沟通负责人记录影响与下一次更新时间。这样排查不会被重复指令打断,恢复动作也有一致记录。
| 阶段 | 资料助手中的动作 | 恢复条件 |
|---|---|---|
| 发现 | 查询失败,核对请求范围与影响 | 已识别受影响用户与数据 |
| 止损 | 暂停写入、关闭危险工具 | 新操作不扩大问题 |
| 恢复 | 在隔离位置复制快照并重放日志 | 校验内容、条数与归属 |
| 恢复流量 | 先少量只读,再恢复写入 | 读写检查和审计均正常 |
| 复盘 | 保存时间线与造成影响的条件 | 改进项有人负责并有验证动作 |
备份需要包含数据、关键配置、索引重建材料和必要恢复凭证,按权限隔离保管。副本要与原系统隔离,并按保留策略定期检查;定期恢复演练检查凭证、操作步骤和恢复后的数据核对。
Agent正在执行的任务还需单独核对:哪个工具已完成、哪个待确认、哪个可以重新执行。邮件发送和订单创建等副作用通过操作ID查询,对账后决定补偿。把“服务重新启动”与“数据和任务恢复完整”分别核对,才适合重新开放功能。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“备份、恢复与事件响应”,哪种做法更稳妥?
对应知识点:L8-incident
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L8-incident
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L8-incident
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L8-incident
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。