TCC分布式事务
原理、架构与设计思想详解
info 定义与背景
TCC(Try-Confirm-Cancel)是一种基于业务补偿的分布式事务解决方案,通过将事务拆分为三个阶段实现最终一致性。在分布式系统中,当业务操作涉及多个独立的服务或数据源时,传统的ACID事务难以满足需求,TCC模式应运而生。
在分布式缓存系统中,如Redis集群,一条指令(如MSET)可能需要多个节点同时执行,可能有些节点成功而另一部分节点失败,导致一致性问题。TCC模式为解决这类问题提供了有效的方案。
layers TCC三个阶段详解
Try(尝试)
完成所有业务检查(一致性),预留必须业务资源(准隔离性)。此阶段仅做资源预留,不执行真正的业务操作。
Confirm(确认)
如果所有分支的Try都成功,则执行Confirm阶段。真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源。
Cancel(取消)
如果任何一个分支的Try失败,则执行Cancel阶段。释放Try阶段预留的业务资源,回滚事务。
people TCC分布式事务中的角色
AP(应用程序)
发起全局事务,定义全局事务包含哪些事务分支。是整个事务流程的发起者和业务逻辑的执行者。
RM(资源管理器)
负责分支事务各项资源的管理。每个参与者都需要实现Try、Confirm、Cancel三个方法,管理自己的资源。
TM(事务管理器)
负责协调全局事务的正确执行,包括Confirm、Cancel的执行,并处理网络异常等故障情况。
compare_arrows TCC与两阶段提交(2PC)的关系
TCC本质上就是一个应用层面的2PC,需要通过业务逻辑来实现。与传统的2PC相比,TCC具有以下特点:
- 2PC通常在跨库的DB层面实现,而TCC在应用层面实现
- TCC可以让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能
- 2PC是阻塞协议,而TCC是非阻塞的,资源锁定时间更短
- TCC具有更好的可扩展性,适用于SOA架构
thumbs_up_down TCC的优缺点分析
优点
- 可以让应用自己定义数据库操作的粒度
- 降低锁冲突,提高吞吐量
- 资源锁定时间短,性能更好
- 适用于SOA架构,可扩展性好
- 实现最终一致性,满足大多数业务场景
缺点
- 对应用的侵入性非常强
- 业务逻辑的每个分支都需要实现三个操作
- 实现难度比较大,开发成本高
- 需要按照不同的失败原因实现不同的回滚策略
- Confirm和Cancel接口必须实现幂等
storage 在Redis集群中实现分布式事务
在Redis集群中,DEL、MSET等命令所涉及的key可能分布在不同的节点中,导致一致性问题。使用TCC模式可以解决此类问题:
- Try阶段:检查所有key是否存在,锁定相关key,防止被其他操作修改
- Confirm阶段:执行真正的MSET操作,释放锁
- Cancel阶段:释放所有锁定的key,不做任何修改
通过这种方式,可以保证MSET操作要么全部成功,要么全部失败,从而实现分布式事务的一致性。
code TCC分布式事务的Golang实现示例
以下是一个简化的TCC事务管理器的Golang实现示例:
type TCCManager struct { participants map[string]Participant // 参与者集合 } type Participant interface { Try() error // Try阶段方法 Confirm() error // Confirm阶段方法 Cancel() error // Cancel阶段方法 } // 执行TCC事务 func (tm *TCCManager) Execute() error { // Try阶段:所有参与者尝试执行 for id, p := range tm.participants { if err := p.Try(); err != nil { // 如果Try失败,执行Cancel回滚 tm.cancelAll() return fmt.Errorf("participant %s Try failed: %v", id, err) } } // Confirm阶段:所有参与者确认执行 for id, p := range tm.participants { if err := p.Confirm(); err != nil { // 如果Confirm失败,记录错误,但无法回滚 // 此时需要人工介入处理 return fmt.Errorf("participant %s Confirm failed: %v", id, err) } } return nil } // 取消所有参与者的操作 func (tm *TCCManager) cancelAll() { for id, p := range tm.participants { if err := p.Cancel(); err != nil { // 记录Cancel失败的日志,需要人工介入 log.Printf("participant %s Cancel failed: %v", id, err) } } }
在实际应用中,还需要考虑网络超时、重试机制、幂等性等问题,以确保分布式事务的可靠性。