Agent 应用开发会员账号
知识目录选择核心方向与细分内容

SYSTEMATIC LEARNING / 分层专题

幂等、未知结果与任务恢复

用调用方和服务方两份独立数据库模拟“提交成功但响应丢失”,看清检查点的保证边界。

学习目标:能够区分执行尝试与业务操作,使用稳定操作身份恢复未知结果,并说明远端不支持去重时的边界。

内容核对 2026-10-04 · 每层有独立讲解、任务与检查

按这个知识点的熟悉程度选择起点。当前层:资深 · 解释取舍。完成任务后可以继续下一层;阅读与自检不自动代表掌握。

本层学习目录

先补齐必要概念

适合:需要设计跨系统任务恢复与审批。

幂等
同一业务操作重复请求时,不额外产生重复业务效果;保证范围要看服务端实现与有效期。
操作身份
表示同一次业务意图的稳定标识,与每次网络尝试的标识不同。
Checkpoint
调用方保存的计算状态,不自动等于外部服务已经发生的事实。
未知结果
调用方没有拿到可靠回执,既不能断言成功,也不能断言没执行。

原理怎样一步步成立?

  1. 持久化意图

    在发起动作前保存稳定操作身份与参数。

  2. 服务方提交

    服务方以自身契约执行与去重。

  3. 调用方核对

    响应丢失时查询或沿同一身份重试。

  4. 更新任务状态

    取得可核对回执后确认,无法核对则保留未知状态。

资深 · 解释取舍

根据服务方能力选择恢复保证

本层目标:能写出保证范围、去重有效期、核对路径和人工升级条件。

先盘点服务方提供什么

支持稳定幂等身份的服务可在契约范围内重复请求;提供业务编号查询的服务可先核对;两者都没有的服务,则必须接受未知结果与重复风险。不能把所有服务装在一个本地状态机后,就统一承诺端到端 exactly-once。

设计身份的作用域与生命周期

身份要包含适当的租户与业务范围,参数摘要用于发现意图变化。去重保留期限必须覆盖允许重试和任务恢复的时间;三天审批等待而远端只保留一天去重记录,会产生边界缺口。过期后怎样查询、拒绝或人工核实需要提前设计。

把恢复当作一次当前决策

恢复时还要检查权限、审批内容、工具版本与资源状态。原批准不自动允许新内容,旧权限也不必然仍有效。方案成本包括持久化账本、核对请求、冲突处理和人工流程,应根据副作用的损失与可逆性选择。

运行实验,观察反例

两份本地 SQLite 文件模拟独立提交与响应丢失;无真实远端、进程终止或网络故障,不证明端到端 exactly-once。

Python 3.10+ · 默认运行只使用标准库 · 在你的电脑运行

  1. 观察调用方 prepared 与服务方已提交的分歧
  2. 沿同一操作身份获取原回执
  3. 用同一身份改变内容,验证冲突
下载 idempotency_recovery.py ↓
python3 idempotency_recovery.py
查看本入口脚本
"""Two local SQLite files model independent caller/provider commits.

Not a real remote service, production queue or end-to-end exactly-once proof.
"""
import json
import sqlite3
import tempfile
from pathlib import Path


def provider(path, operation, payload):
    with sqlite3.connect(path) as db:
        db.execute("CREATE TABLE IF NOT EXISTS effects (operation TEXT PRIMARY KEY, payload TEXT NOT NULL, receipt TEXT NOT NULL)")
        # Serializes the read/check/write in this local demonstration.
        db.execute("BEGIN IMMEDIATE")
        existing = db.execute("SELECT payload,receipt FROM effects WHERE operation=?", (operation,)).fetchone()
        if existing:
            if existing[0] != payload:
                raise ValueError("same operation with different payload")
            return existing[1]
        receipt = "receipt:" + operation
        db.execute("INSERT INTO effects VALUES(?,?,?)", (operation, payload, receipt))
        return receipt


def demo():
    with tempfile.TemporaryDirectory() as folder:
        remote, local = Path(folder) / "provider.db", Path(folder) / "caller.db"
        with sqlite3.connect(local) as db:
            db.execute("CREATE TABLE intents(operation TEXT PRIMARY KEY, payload TEXT, status TEXT, receipt TEXT)")
            db.execute("INSERT INTO intents VALUES('publish-1','report-v1','prepared',NULL)")
        first = provider(remote, "publish-1", "report-v1")
        # Simulated response loss: provider committed, caller did not get receipt.
        with sqlite3.connect(local) as db:
            before = db.execute("SELECT status FROM intents").fetchone()[0]
        second = provider(remote, "publish-1", "report-v1")
        with sqlite3.connect(local) as db:
            db.execute("UPDATE intents SET status='confirmed', receipt=?", (second,))
        with sqlite3.connect(remote) as db:
            effects = db.execute("SELECT COUNT(*) FROM effects").fetchone()[0]
        assert before == "prepared" and first == second and effects == 1
        return dict(caller_before_recovery=before, same_receipt=first == second,
                    provider_effects=effects, caller_after_recovery="confirmed")


if __name__ == "__main__":
    print(json.dumps(demo(), sort_keys=True))

本地运行的预期输出

{"caller_after_recovery": "confirmed", "caller_before_recovery": "prepared", "provider_effects": 1, "same_receipt": true}
  • 服务方业务效果只有一条
  • 同意图重试回执一致
  • 远端能力和去重期限需另行核实
查看运行环境、输出和校验记录 →

继续做进阶研究实验

进程真的退出后,还能怎样继续?

从两份数据库的幂等反例,继续验证租约、检查点和独立服务方回执。解压可靠性实验 v3,在独立目录执行。

阅读全文与故障分析 → · 下载可靠性实验 v3 ↓

python3 cli.py submit --db crash.sqlite
python3 cli.py run --db crash.sqlite --lease-seconds 2 --fault after_collect
python3 cli.py inspect --db crash.sqlite
# 首次运行预期退出码 75;等待至少 2 秒后分别执行
python3 cli.py run --db crash.sqlite
python3 evaluate.py --db crash.sqlite

保留证据,逐条核对

  • 首次 inspect 显示 running,已保存 collect 检查点。
  • 租约过期后 succeeded,generation=2,collect 只提交一次。
  • 按 README 再跑 after_effect,核对独立 publisher 数据库只有一个 receipt。

固定 collect → draft → verify → publish 流程;验证本地持久协议与真实进程退出,不证明任意远程服务恰好一次执行。

本层验收任务

为报告发布与不可查询的邮件服务分别写恢复方案,明确两者能够保证什么。

完成后逐条核对

  • 区分远端去重、查询与两者皆无三类能力
  • 说明去重期限、身份冲突与恢复权限
  • 写出人工核对和补偿失败后的终态

保存自己的过程、代码与结果。这里提供验收要求,暂不自动评分或保存课程掌握状态。

收起答案,检查理解

为什么本课实验不能证明任何第三方接口都只执行一次?

延伸原理与知识练习

遇到不熟悉的原理,先阅读实现、连续追问和迁移案例,再独立说明前提与边界。作答与笔记保存到原有账号记录。

本专题的全部关联解析与练习(5 道)

依据与验证范围

原理依据来自公开资料;数字、案例和任务是本站教学设计。离线实验验证本页注明的范围,学习效果仍需通过独立任务与反馈判断。