# SAGA 分布式事务设计汇报 > 基于 `gvsdsdk/msync` + `gvsds/apps/msync` 源码分析(2026-08-10)。 > 涉及文件:`gvsds/apps/msync/transaction.py`、`gvsds/apps/msync/models.py`、`gvsds/apps/msync/local_tx.py`、`gvsdsdk/msync/local_tx.py`、`gvsds/apps/msync/MSYNC.md`。 --- ## 1. 架构定位:双包三层 ``` ┌────────────────────────────── 主服务器(账本服务器)──────────────────────────────┐ │ gvsds/apps/msync/transaction.py TransactionManager(Saga 编排引擎) │ │ gvsds/apps/msync/models.py DistributedTransaction / TransactionStep │ │ │ 正向: POST {domain}/api/msync/local/execute/ {StepName, payload} │ │ │ 反向: POST {domain}/api/msync/local/compensate/ {StepName, payload, │ │ │ ActionResult} │ └────────┼───────────────────────────────┬─────────────────────────────────────────┘ ▼ ▼ ┌────────────────────────┐ ┌────────────────────────┐ │ gvsdsdk/msync/ │ │ gvsdsdk/msync/ │ │ local_tx.py │ │ transaction.py │ │ LocalTransactionManager│ │ 薄封装 → 转发 gvsds 原生层│ │ (工作服务本地执行端) │ │ (仅主服务器可用) │ └────────────────────────┘ └────────────────────────┘ ``` 关键事实: - **编排端在主服务器,执行端在每个工作服务**,通过账本(`ServiceNode.ServiceDomain`)发现参与服务域名,HTTP 调用本地端点。 - **SDK 薄封装**:`gvsdsdk/msync/transaction.py` 与 `ogm.py` 仅转发 gvsds 原生层,子服务器无 gvsds 依赖时抛 ImportError;`local_tx.py` 是**真实实现**,子服务器可直接注册步骤处理器。 - **主服务器本身也当工作服务**:`gvsds/apps/msync/local_tx.py` 从 gvsdsdk 导入 `LocalTransactionManager`,主服务器同样可以参与 Saga 步骤。 - 双包环形依赖:gvsdsdk(SDK)→ gvsds(主服务器)→ gvsdsdk(local_tx),靠惰性加载(PEP 562)避免初始化时序问题。 ## 2. 状态机 ### 事务 7 态(`DistributedTransaction`) ``` Pending(0) ──▶ Running(1) ──▶ Committed(2) (全部步骤成功) │ ├─▶ Failed(5) ──▶ Compensating(3) ──▶ Compensated(4) │ └─▶ TimedOut(6) ──▶ Compensating(3) ──▶ Compensated(4) ``` 终态集合(`IsTerminal`)= Committed / Compensated / Failed / TimedOut。 ### 步骤 7 态(`TransactionStep`) ``` Pending(0) ──▶ Running(1) ──▶ Completed(2) │ ├─▶ Compensating(3) ──▶ Compensated(4) └─▶ Failed(5)(正向失败)/ Skipped(6)(事务取消跳过) ``` ### 关键属性 - `IsTimedOut`:`CreateTime + TimeoutSeconds`(默认 300s)判定,已终态恒 False。 - `CanRetry`:`RetryCount < MaxRetries`(默认 MaxRetries=3)。 ## 3. 核心流程 ### 3.1 正向执行(`Execute`) 1. `select_for_update()` 锁行(防并发推进同一事务)。 2. 校验状态 ∈ {Pending, Running};超时则标记 TimedOut 并 `on_commit` 补偿。 3. 置 Running → 按 `StepIndex` 逐步骤执行: - 已 Completed 步骤跳过(断点续跑语义); - `_execute_step`:标记步骤 Running → POST 参与服务 execute 端点 → 返回结果 → 标记 Completed + 保存 StepResult; - 任一失败:步骤标记 Failed,记录错误快照,`on_commit` 触发 `Compensate`。 4. 全部完成 → 事务 Committed。 ### 3.2 反向补偿(`Compensate`) 1. `select_for_update()` 锁行;已 Compensated 直接返回(幂等)。 2. 事务置 Compensating → **逆序**遍历 `StepStatus=Completed` 的步骤。 3. 每步 `_compensate_step`:把该步骤原始 `StepResult` 作为 `ActionResult` 随请求传回 → 业务补偿函数可据此幂等回滚。 4. 补偿成功 → 步骤 Compensated;补偿失败 → 记录 `compensate_error`(**步骤状态停留在 Compensating**)。 5. 若存在仍在 Compensating 的步骤 → 事务 Failed;否则 Compensated。 ### 3.3 超时与兜底 - 双保险:执行中逐步骤检测 `IsTimedOut`(`on_commit` 补偿)+ `CheckTimeouts()` 轮询兜底(需 Celery 定时任务触发)。 ### 3.4 重试 - **手动重试**:`RetryStep(transaction_code, step_index)` 仅允许 Failed/Pending 步骤,校验 `CanRetry`,重跑 action。 - **注意**:Execute 正向流程中**没有自动重试**逻辑——步骤失败直接进入补偿,`MaxRetries` 仅约束手动重试次数。 ## 4. 传输层设计 | 项 | 配置 | |---|---| | 会话 | 模块级 `requests.Session`,连接池 10 | | TLS | 显式 `verify=True`,支持 `REQUESTS_CA_BUNDLE` 环境变量自定义 CA | | 重试 | 502/503/504 自动重试 3 次,backoff 0.5s,仅 POST | | 超时 | 单次请求 30s | | 鉴权 | `Authorization: Service `(HMAC 验权签发,2h 有效) | | 响应契约 | 兼容 `{code:200, data}`(DRF)与 `{success, result, error}`(本地管理器) | ## 5. 设计亮点 1. **远程编排 + 本地执行彻底解耦**:子服务器仅依赖 SDK `local_tx` 注册处理器即可接入,无需理解编排。 2. **补偿带回 ActionResult**:补偿函数拿到原始执行结果,可精确、幂等地回滚(如按返回的 user_uuid 删除)。 3. **行锁防并发**:Execute / Compensate 均 `select_for_update`。 4. **on_commit 补偿**:补偿在主事务提交后触发,避免"正向已回滚却补偿"的错乱。 5. **断点续跑**:Execute 跳过已完成步骤,支持中途失败后整体重跑推进。 6. **协议双兼容**:`_call_service` 同时解析两套响应契约。 ## 6. 缺陷与风险 | 等级 | 问题 | 位置 | 说明 | |---|---|---|---| | 高 | 响应非 JSON 时事务卡死 | `_call_service`(transaction.py L538-566) | `resp.json()` 解析失败抛 JSONDecodeError 未捕获 → Execute 中断 → 事务永久停 Running,不触发补偿,需等 300s 超时 | | 高 | 补偿失败后无法重试补偿 | `Compensate`(L256-314) | 补偿异常时步骤状态停在 Compensating(非 Completed),再次 Compensate 查不到它 → 永远补不回来,事务 Failed 只能人工处理,无 re-compensate 入口 | | 中 | MaxRetries 无自动重试 | `Execute`(L211-244) | 失败直接补偿,`CanRetry` 只在手动 RetryStep 生效;RetryStep 无 `select_for_update`,并发重试有竞态 | | 中 | 无步骤间数据流 | `Execute`/`_execute_step` | `StepResult` 不注入后续步骤 payload;TransactionPayload 创建后不参与执行。无法表达"A 步骤输出是 B 步骤输入"的链式场景 | | 中 | 超时以 CreateTime 起算 | `models.py IsTimedOut` | 非"最近活动空闲超时",10 步慢事务总预算仅 300s,无活动推进重置机制 | | 低 | TransactionResult 恒空 | `Execute`/`Compensate` | 模型有字段但从不写入,`GetDetail().Result` 永远 null | | 低 | 幂等依赖业务自觉 | `_call_service` | 无全局幂等键,崩溃窗口内重复执行 action 的副作用靠业务方补偿兜底 | ## 7. 改进建议(按优先级) 1. `_call_service` 捕获 JSON 解析/异常 → 统一转 `StepExecutionError`,保证失败必进补偿链路。 2. 补偿失败步骤状态改为 Failed 并提供 `RetryCompensate` 入口(复用 RetryStep 骨架 + 行锁)。 3. `RetryStep` 补 `select_for_update`;若需自动重试,在 Execute 循环内按 `CanRetry` 内联重试。 4. 支持步骤结果注入下一步 ActionPayload(可选项,向后兼容)。 5. 超时改为基于 `UpdateTime` 的空闲判定或提供事务级 heartbeat。 6. Execute/Compensate 末尾统一写入 `TransactionResult` 汇总。 --- *汇报人:TraeCode(2026-08-10)*