# TccTransactionLab **Repository Path**: ymjake/tcc-transaction-lab ## Basic Information - **Project Name**: TccTransactionLab - **Description**: 基于ASP.NET Core的TCC分布式事务实验项目 - **Primary Language**: C# - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-10 - **Last Updated**: 2026-05-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: TCC ## README # TCC Transaction Lab 一个独立部署的最小 `TCC` 协调服务,使用: - `ASP.NET Core Minimal API` - `JSON 文件` 持久化事务日志 - 后台恢复任务重试 `Confirm/Cancel` - 参与者通过 `HTTP POST` 接口接入 当前已经按三层拆分: - `Tcc.Transaction` - `Tcc.Transaction.Contracts` - `Tcc.Transaction.Coordinator` - `Tcc.Transaction.Coordinator.Http` 职责边界: - `Tcc.Transaction`:事务模型与 JSON 事务存储 - `Tcc.Transaction.Contracts`: 是跨模块共享的 API 契约层,负责定义 TCC 协调服务与外部系统交互的 HTTP 请求/响应模型。 - `Tcc.Transaction.Coordinator`:TCC 协调核心与参与者调用器 - `Tcc.Transaction.Coordinator.Http`:HTTP 宿主、路由、配置、后台恢复任务 ## 运行 ```powershell dotnet run --project .\Tcc.Transaction.Coordinator.Http\Tcc.Transaction.Coordinator.Http.csproj --urls http://127.0.0.1:5099 ``` 默认事务日志位置: ```text tcc-transaction.json ``` 开发环境会写到: ```text tcc-transaction.json ``` ## 参与者接口 协调器会对每个分支调用三类接口: - `tryUrl` - `confirmUrl` - `cancelUrl` 请求体格式: ```json { "transactionId": "5a6010e6e86b4e2eb1b5d9a707bbf446", "branchId": "a95db2c425dd4467b3818d5af13d4f40", "businessKey": "order-1001", "participant": "account-service", "phase": "Try", "fenceToken": "5a6010e6e86b4e2eb1b5d9a707bbf446:a95db2c425dd4467b3818d5af13d4f40:try", "phaseAttempt": 1, "recoveryAttempt": 0, "payload": { "userId": 1, "amount": 100 } } ``` 只要参与者返回 `2xx`,协调器就认为该阶段执行成功。 参与者如果要做幂等或空回滚防护,建议至少基于下面几个字段建 fence: - `transactionId` - `branchId` - `phase` - `fenceToken` 其中: - `fenceToken` 对同一事务分支的同一阶段保持稳定,不会因为重试而变化 - `phaseAttempt` 是当前阶段的调用次数,第一次调用为 `1` - `recoveryAttempt` 是协调器级恢复次数,首次正常流程为 `0` ## 发起事务 ```http POST /api/tcc/transactions ``` 请求体示例: ```json { "title": "create-order", "businessKey": "order-1001", "maxRetryCount": 8, "retryIntervalSeconds": 10, "branches": [ { "participant": "account-service", "tryUrl": "http://127.0.0.1:6001/tcc/try", "confirmUrl": "http://127.0.0.1:6001/tcc/confirm", "cancelUrl": "http://127.0.0.1:6001/tcc/cancel", "payload": { "userId": 1, "amount": 100 } }, { "participant": "inventory-service", "tryUrl": "http://127.0.0.1:6002/tcc/try", "confirmUrl": "http://127.0.0.1:6002/tcc/confirm", "cancelUrl": "http://127.0.0.1:6002/tcc/cancel", "payload": { "skuId": 10001, "count": 2 } } ] } ``` ## 查询与恢复 - `GET /api/tcc/transactions` - `GET /api/tcc/transactions/by-business-key/{businessKey}` - `GET /api/tcc/transactions/manual` - `GET /api/tcc/transactions/{transactionId}` - `POST /api/tcc/transactions/{transactionId}/retry` - `POST /api/tcc/transactions/{transactionId}/manual/resume` - `POST /api/tcc/transactions/{transactionId}/manual/force-complete` 按 `businessKey` 查询事务: ```http GET /api/tcc/transactions/by-business-key/order-1001 ``` 返回值为同一 `businessKey` 下的事务列表,按 `updatedAtUtc` 倒序排列,适合用来排障和回查同一业务单号的重试历史。 `retry` 现在除了可继续 `Confirming/Cancelling` 状态,也可以在 `FailedManual` 之后继续未完成阶段。 ### 人工干预 列出人工介入队列: ```http GET /api/tcc/transactions/manual ``` 继续一个已进入 `FailedManual` 的事务: ```http POST /api/tcc/transactions/{transactionId}/manual/resume ``` 人工强制完结一个已进入 `FailedManual` 的事务: ```http POST /api/tcc/transactions/{transactionId}/manual/force-complete Content-Type: application/json ``` 请求体示例: ```json { "status": "Cancelled", "note": "operator forced rollback after downstream data fix" } ``` 说明: - `status` 只支持 `Confirmed` 或 `Cancelled` - 强制完结会把当前事务收敛到终态,并同步把对应分支状态标记为完成 - `manual/force-complete` 当前只更新协调器中的事务/分支状态,不会再次调用参与者的 `Confirm/Cancel` 接口 - `note` 会持久化到事务日志,便于后续排查 ### 分支调用观测 每个分支现在会持久化最近一次参与者调用的关键信息: - `lastParticipantCallAtUtc` - `lastParticipantCallPhase` - `lastParticipantCallUrl` - `lastParticipantCallSucceeded` - `lastParticipantResponseStatusCode` - `lastParticipantResponseSummary` 开发样例可见 `Tcc.Transaction.Coordinator.Http/tcc-transactions.dev.json`,其中展示了成功调用时的响应码、响应摘要和最后调用时间。 ## 事务状态流转图 下图展示了当前实现中的主状态流转、后台自动恢复链路,以及 `manual/resume` 与 `manual/force-complete` 两类人工干预路径。 ![TCC 事务状态流转图](docs/tcc-state-flow.png) ## 实验记录 以下链路已在 `2026-05-10` 完成验证,事务明细均已写入 `Tcc.Transaction.Coordinator.Http/tcc-transaction.json`。 | 场景 | 订单号 | 事务号 | 最终结果 | 关键观察 | | --- | --- | --- | --- | --- | | 成功提交 | `order-1001` | `8660e870c6a9469691561d62535c6023` | `Confirmed` | 三个分支 `Try` 成功后,`Confirm` 全部成功,事务正常结束 | | `Try` 失败触发全局回滚 | `order-2001` | `7f61a03b2b5343009b24d1d236d13214` | `Cancelled` | `payment-api` 的 `Try` 失败后,已成功 `Try` 的分支进入 `Cancel`,全局事务回滚完成 | | `Confirm` 首次失败后自动重试恢复 | `order-3001` | `b2fa7edf010f48d78cb636cd137b0ce3` | `Confirmed` | `seat-inventory-api` 首次 `Confirm` 失败,事务停留在 `Confirming`,后台恢复线程二次执行后成功;全局 `retryCount = 1`,库存分支 `confirmAttemptCount = 2` | | `Confirm` 连续失败超过重试上限 | `order-4001` | `24d2ac3377564a27bae1dd1fe7d1b90e` | `FailedManual` | `seat-inventory-api` 的 `Confirm` 连续失败 8 次后,事务进入人工队列,`currentPhase = Confirm` | | `FailedManual` 后人工恢复 | `order-4001` | `24d2ac3377564a27bae1dd1fe7d1b90e` | `Confirmed` | 清除库存侧故障注入后调用 `POST /api/tcc/transactions/{transactionId}/manual/resume`,协调器继续实际执行未完成的 `Confirm`,事务恢复成功,库存分支 `confirmAttemptCount = 9` | | `manual/force-complete` 强制收敛为 `Confirmed` | `order-5001` | `601d355b78dd4825a7a263c7d6249b20` | `Confirmed` | 事务原本处于 `FailedManual + currentPhase=Confirm`。调用 `manual/force-complete` 后,协调器事务与分支状态被直接改为 `ConfirmSucceeded`,但业务侧未补执行:`FlightBooking` 仍为 `PendingConfirm`,`SeatInventory` 仍为 `Reserved` | | `manual/force-complete` 强制收敛为 `Cancelled` | `order-5002` | `5cab29ebe6384d80aff473e12d7ab484` | `Cancelled` | 事务原本处于 `FailedManual + currentPhase=Cancel`。调用 `manual/force-complete` 后,协调器事务与分支状态被直接改为 `CancelSucceeded`,但业务侧未补执行:`FlightBooking` 仍为 `PendingConfirm`,`SeatInventory` 仍为 `Reserved` | | `Try` 半途中卡住后自动恢复 | `order-stucktry-1778388950` | `203b0f113821432f917b73179e87a6d3` | `Confirmed` | 先让 `flight-booking-api` 的 `Try` 成功,再人工构造 `Trying + NextRetryAtUtc=null + UpdatedAtUtc 超时` 的卡住事务。后台恢复线程随后继续未完成的 `Try`,并自动进入 `Confirm`;三个分支最终均为 `ConfirmSucceeded`,业务侧订单/库存/支付状态与协调器一致 | ### 实验结论 - `retry` 和 `manual/resume` 都会继续执行真实的参与者接口调用 - `manual/force-complete` 当前语义是协调器侧强制收口,不是补执行接口 - 对于 `Trying/Confirming/Cancelling` 且尚未写入 `NextRetryAtUtc` 的卡住事务,后台恢复线程也会基于 `UpdatedAtUtc` 超时阈值继续推进 - 如果要让业务数据和协调器状态重新一致,应先修复下游数据或接口,再使用 `manual/resume` - `manual/force-complete` 应视为人工记账完成,而不是技术补偿完成 ## 当前范围 当前版本已经具备: - 全局事务状态持久化 - 按 `businessKey` 查询事务 - 分支事务状态持久化 - 分支级 phase fence token 持久化 - 分支级最近一次参与者调用观测持久化 - 顺序 `Try` - 逆序 `Confirm` - 逆序 `Cancel` - `Confirm/Cancel` 失败后的后台重试 - 卡住的 `Trying/Confirming/Cancelling` 事务自动续跑 - `FailedManual` 后基于阶段上下文继续恢复 - 人工列队查询、人工恢复、人工强制完结 下一步更适合补: - 更细的错误码和回查能力 - 数据库存储替换 JSON 文件