Files
SMANAGE/SAGA_Report.md

135 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <AccessToken>`(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)*