AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

备份、恢复与事件响应

我的学习 →

正在读取学习记录…

资料助手保存了五条业务笔记。12分备份时只有前三条,16分和19分又分别写入第四、第五条。20分服务故障,用户关心两件事:什么时候可以重新使用,刚刚保存的内容是否还在。

备份是为恢复保留数据副本;恢复是把副本及后续变更装入可用系统并核对。事件响应是故障期间的协作过程,包括识别、止损、调查、恢复、沟通和复盘。正常恢复流程先暂停写入,选择可信快照,重放后续变更,验证数据与权限,最后恢复流量。

时间目标和数据目标怎样分别描述

RTO(恢复时间目标)表示允许服务中断多久;RPO(恢复点目标)表示最多允许丢失多长时间内的数据。假设业务要求RTO不超过10分钟、RPO不超过5分钟:20分发生故障、26分恢复,演练时间线给出6分钟中断;只恢复12分快照,会失去16分和19分两笔已确认写入,按故障时刻算恢复点落后8分钟。

备份、恢复与事件响应机制图
12分快照含1至3,日志补齐16分和19分的4、5;20分止损,26分恢复后核对完整内容。

图中快照覆盖1–3,日志保存4、5。恢复时先复制快照,再按ID补齐日志。19分之后没有新确认写入;完整日志恢复后,已确认写入没有丢失。代码另报“已确认写入覆盖缺口”,以最后确认写入19分减去恢复水位计算:只用快照为7分钟,完整重放为0。这个值与故障时刻到快照的8分钟采用不同参照点,要分别解读。

参照快照恢复快照加完整日志
数据条数35
最新覆盖写入时刻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 创建并进入目录,再保存下方两份文件:

PowerShell
New-Item -ItemType Directory -Force l8-incident
cd l8-incident

macOS 终端:

Bash
mkdir -p l8-incident
cd l8-incident
Python
"""执行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:

Python
"""核对恢复内容、快照缺口、重复重放和冲突,不依赖原库。"""
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()

文件保存后,在同一窗口核对文件名并运行:

Bash
ls
python -X utf8 restore_notes.py
python -X utf8 -m unittest -v test_restore_notes.py

dir应显示这两个文件。运行程序后得到下面输出;测试末尾应显示 OK。

程序输出:

TXT
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,运行:

Bash
python -X utf8 restore_notes.py

恢复ID变成 [1, 2, 3],内容核对变成“缺少已确认写入”,覆盖缺口变成7分钟,演练恢复时间仍为6分钟。恢复 REPLAY_JOURNAL = True 并再次运行,得到五条笔记和0分钟缺口。

测试还重放重复日志、冲突日志和缺少第五条的日志。重复不增加条数,冲突触发错误,不完整日志导致核对失败。真实恢复还要确认日志的持久化、连续性和提交边界;单靠最大时间戳无法证明中间没有缺失记录。

故障时怎样分工并逐步恢复

Google SRE 的事件管理章节介绍了集中协调和清晰分工。事件指挥负责决定优先级与控制变更,技术处理者执行止损和恢复,沟通负责人记录影响与下一次更新时间。这样排查不会被重复指令打断,恢复动作也有一致记录。

阶段资料助手中的动作恢复条件
发现查询失败,核对请求范围与影响已识别受影响用户与数据
止损暂停写入、关闭危险工具新操作不扩大问题
恢复在隔离位置复制快照并重放日志校验内容、条数与归属
恢复流量先少量只读,再恢复写入读写检查和审计均正常
复盘保存时间线与造成影响的条件改进项有人负责并有验证动作

备份需要包含数据、关键配置、索引重建材料和必要恢复凭证,按权限隔离保管。副本要与原系统隔离,并按保留策略定期检查;定期恢复演练检查凭证、操作步骤和恢复后的数据核对。

Agent正在执行的任务还需单独核对:哪个工具已完成、哪个待确认、哪个可以重新执行。邮件发送和订单创建等副作用通过操作ID查询,对账后决定补偿。把“服务重新启动”与“数据和任务恢复完整”分别核对,才适合重新开放功能。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“备份、恢复与事件响应”,哪种做法更稳妥?

对应知识点:L8-incident

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L8-incident

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L8-incident

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L8-incident

回到L8总览串起权限、发布与恢复,然后进入L9学习总览。

图片查看器
100%

文章配图 · 按 Esc 关闭