概述
silky 框架提供了基于 TCC(Try-Confirm-Cancel) 模式的分布式事务支持。TCC 是一种应用层分布式事务解决方案:
- Try:预留资源,执行业务检查和资源锁定
- Confirm:确认执行,使用 Try 阶段预留的资源完成业务
- Cancel:撤销执行,释放 Try 阶段预留的资源
相比两阶段提交(2PC),TCC 对底层资源(数据库、消息队列等)没有侵入性要求,适用于跨多个微服务的业务一致性场景。
使用方式
在服务接口方法上标注 [TccTransaction] 特性,并指定 Confirm 和 Cancel 的方法名:
public interface IAccountAppService
{
/// <summary>
/// Try 阶段:扣减账户余额(预留)
/// </summary>
[TccTransaction(ConfirmMethod = "DeductBalanceConfirm", CancelMethod = "DeductBalanceCancel")]
Task<bool> DeductBalance(DeductBalanceInput input);
/// <summary>
/// Confirm 阶段:确认扣减(提交)
/// </summary>
Task<bool> DeductBalanceConfirm(DeductBalanceInput input);
/// <summary>
/// Cancel 阶段:恢复余额(回滚)
/// </summary>
Task<bool> DeductBalanceCancel(DeductBalanceInput input);
}
TccTransactionAttribute 的定义:
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class TccTransactionAttribute : Attribute, ITccTransactionProvider
{
public string ConfirmMethod { get; set; } // Confirm 阶段的方法名
public string CancelMethod { get; set; } // Cancel 阶段的方法名
}
角色与流程
发起者(Starter)
发起者是 TCC 事务的入口,通常是业务流程的编排者(如订单服务)。发起者负责:
- 调用多个参与者的 Try 方法
- 所有 Try 成功后,触发全局 Confirm
- 任意 Try 失败时,触发全局 Cancel
参与者(Participant)
参与者是被发起者远程调用的微服务(如账户服务、库存服务)。每个参与者:
- 接收 Try 请求,预留资源
- 根据发起者的最终决定,执行 Confirm 或 Cancel
完整时序
订单服务(发起者) 账户服务(参与者) 库存服务(参与者)
│ │ │
│── Try: DeductBalance ─────────▶ │
│── Try: ReduceStock ───────────────────────────────────▶
│ │ │
所有 Try 成功
│ │ │
│── Confirm: DeductBalanceConfirm ────────────────────▶ │
│── Confirm: ReduceStockConfirm ────────────────────────▶
│ │
[如果任意 Try 失败]
│── Cancel: DeductBalanceCancel ──────────────────────▶ │
│── Cancel: ReduceStockCancel ──────────────────────────▶
事务协调器(TccTransactionExecutor)
TccTransactionExecutor 是 TCC 事务的核心协调器,以单例懒加载(Lazy<T>)方式初始化:
public sealed class TccTransactionExecutor
{
private static readonly Lazy<TccTransactionExecutor> _lazyExecutor =
new(() => new TccTransactionExecutor(),
LazyThreadSafetyMode.ExecutionAndPublication);
public static TccTransactionExecutor Executor => _lazyExecutor.Value;
}
主要方法:
| 方法 | 角色 | 说明 |
|---|---|---|
PreTry(invocation) | 发起者 | 创建事务记录,注册参与者,设置 Try 状态 |
PreTryParticipant(context, invocation) | 参与者 | 注册参与者到事务,设置 Try 状态 |
GlobalConfirm(transaction) | 发起者 | 遍历所有参与者,逐个调用 Confirm |
GlobalCancel(transaction) | 发起者 | 遍历所有参与者,逐个调用 Cancel |
UpdateStartStatus(transaction) | 发起者 | 更新事务状态为 Trying |
Remove() | 通用 | 清理当前线程的事务上下文 |
事务处理器
TransactionTccModule 通过命名注入注册了两个事务处理器:
// 发起者处理器
builder.RegisterType<StarterTccTransactionHandler>()
.Named<ITransactionHandler>(TransactionRole.Start.ToString());
// 参与者处理器
builder.RegisterType<ParticipantTccTransactionHandler>()
.Named<ITransactionHandler>(TransactionRole.Participant.ToString());
框架根据当前请求的 TransactionContext.TransactionRole 解析对应的处理器。
StarterTccTransactionHandler
发起者处理流程:
public async Task Handler(TransactionContext context, ISilkyMethodInvocation invocation)
{
// 1. 创建事务,注册参与者(PreTry)
var transaction = await executor.PreTry(invocation);
SilkyTransactionHolder.Instance.Set(transaction);
// 2. 传播事务上下文(通过 RpcContext Attachments 传递到下游)
var transactionContext = new TransactionContext
{
Action = ActionStage.Trying,
TransId = transaction.TransId,
TransactionRole = TransactionRole.Start,
TransType = TransactionType.Tcc
};
SilkyTransactionContextHolder.Instance.Set(transactionContext);
try
{
// 3. 执行 Try 方法(其中会远程调用各参与者的 Try 逻辑)
await invocation.ProceedAsync();
transaction.Status = ActionStage.Trying;
await executor.UpdateStartStatus(transaction);
// 4. 所有 Try 成功 → 执行全局 Confirm
await executor.GlobalConfirm(currentTransaction);
}
catch (Exception ex)
{
// 5. 任意 Try 失败 → 执行全局 Cancel
await executor.GlobalCancel(errorCurrentTransaction);
throw;
}
finally
{
SilkyTransactionContextHolder.Instance.Remove();
executor.Remove();
}
}
ParticipantTccTransactionHandler
参与者根据收到的 ActionStage 执行不同操作:
switch (context.Action)
{
case ActionStage.Trying:
// Try 阶段:执行预留资源逻辑,记录参与者
participant = await _executor.PreTryParticipant(context, invocation);
await invocation.ProceedAsync();
break;
case ActionStage.Confirming:
// Confirm 阶段:从缓存或仓储加载参与者,执行 ConfirmMethod
await _executor.ParticipantConfirm(invocation, participantList, context.ParticipantId);
break;
case ActionStage.Canceling:
// Cancel 阶段:从缓存或仓储加载参与者,执行 CancelMethod
await _executor.ParticipantCancel(invocation, participantList, context.ParticipantId);
break;
}
事务上下文传播
TCC 事务上下文通过 RpcContext 的 Attachments(附件字典)在微服务间透传,与 HTTP Header 的传播机制类似:
发起者设置 TransactionContext
│
▼(通过 RpcContext.Attachments 序列化到 TransportMessage)
DotNetty RPC 传输
│
▼(参与者端从 Attachments 反序列化 TransactionContext)
参与者判断当前请求是否在事务中
│
├── context.Action == Trying → 执行业务,注册参与者
├── context.Action == Confirming → 执行 ConfirmMethod
└── context.Action == Canceling → 执行 CancelMethod
事务持久化(TransRepositoryStore)
事务状态通过 TransRepositoryStore 持久化,避免因服务重启导致事务数据丢失。默认实现基于 Redis(Silky.Transaction.Repository.Redis)。
持久化的核心对象:
| 对象 | 说明 |
|---|---|
ITransaction | 全局事务记录:TransId、Status、Participants 列表 |
IParticipant | 参与者记录:ParticipantId、TransId、Role、Status、ConfirmMethod、CancelMethod |
主要操作:
| 操作 | 说明 |
|---|---|
CreateTransaction | 创建事务记录 |
CreateParticipant | 创建参与者记录 |
UpdateTransactionStatus | 更新事务状态(Trying → Confirming / Canceling) |
UpdateParticipantStatus | 更新参与者状态 |
RemoveTransaction | 删除已完成的事务记录 |
LoadParticipant | 从仓储加载参与者(用于服务重启后的事务恢复) |
事务恢复调度器
TccTransactionRecoveryService 是 TCC 的定期恢复调度器,以 ITransactionRecoveryService 的命名方式注册:
builder.RegisterType<TccTransactionRecoveryService>()
.Named<ITransactionRecoveryService>(TransactionType.Tcc.ToString());
调度器定期扫描仓储中未完成的事务(状态为 Trying 或 Confirming/Canceling 的),重新触发 Confirm 或 Cancel,确保分布式事务最终一致性。
启用 TCC 事务
在服务宿主中引入 TransactionTccModule,并配置 Redis 仓储:
// 方式一:通过模块依赖
[DependsOn(typeof(TransactionTccModule))]
public class OrderHostModule : SilkyModule { }
// 方式二:使用扩展方法(如果框架提供)
services.AddTransactionTcc();
添加事务仓储(Redis):
services.AddRedisTransactionRepository(configuration);
注意事项
Warning
Try 方法幂等性:Try 方法应保证幂等,因为在网络超时等异常情况下,框架可能重试 Try 请求。
Confirm/Cancel 幂等性:Confirm 和 Cancel 方法必须保证幂等,调度器在恢复时可能重复执行。
Cancel 方法的空回滚处理:Cancel 方法需要处理 Try 阶段未执行成功(资源未预留)时的空回滚情况,避免因 Cancel 方法读取不到预留数据而报错。
方法名约定:
ConfirmMethod和CancelMethod必须是同一接口中的方法名,且方法签名(参数列表)应与 Try 方法保持一致,以便框架正确调用。事务传播范围:TCC 上下文通过
RpcContext.Attachments自动传播,仅对 silky RPC 调用有效,不覆盖 HTTP 调用(HTTP 调用不参与 TCC 事务)。
