分布式事务框架Seata
分布式基础
CAP 定理
CAP 定理指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。在分布式系统中,分区容错性是必须需要实现的。所以只能在一致性和可用性之间进行权衡(AP 或者 CP)。
BASE 理论
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。是对 CAP 中 AP 的一个扩展
- BA 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
- S 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。
- E 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。
BASE 解决了 CAP 中理论没有网络延迟,在 BASE 中用软状态和最终一致,保证了延迟后的一致性。
BASE 和 ACID 是相反的,它完全不同于 ACID 的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
分布式事务实现方式
- XA 方案(两阶段提交)
- TCC 方案
- 本地消息表
- 可靠消息最终一致性方案
- 最大努力通知方案
Seata简介
Seata (Simple Extensible Autonomous Transaction Architecture) 是阿里巴巴开源的分布式事务中间件,,解决微服务场景下面临的分布式事务问题。
具体看 Seata 官网:
Seata主要由三个重要组件组成:
- Transaction Coordinator(TC):管理全局的分支事务的状态,用于全局性事务的提交和回滚。
- Transaction Manager(TM):事务管理器,用于开启全局事务、提交或者回滚全局事务,是全局事务的开启者。
- Resource Manager(RM):资源管理器,用于分支事务上的资源管理,向 TC 注册分支事务,上报分支事务的状态,接受 TC 的命令来提交或者回滚分支事务。
Seata 两种模式
Seata 关注的就是微服务架构下的数据一致性问题,是一整套的分布式事务解决方案。Seata 框架包含两种模式,一种是 AT 模式。AT 模式主要从数据分片的角度,关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题。
另外一个就是 TCC 模式,TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题,保证读资源访问的事务属性。
AT 模式
AT 模式是通过两段提交的方式实现,AT 模式下,把每个数据库被当做是一个 Resource,Seata 里称为 DataSource Resource。业务通过 JDBC 标准接口访问数据库资源时,Seata 框架会对所有请求进行拦截,做一些操作。每个本地事务提交时,Seata RM(Resource Manager,资源管理器) 都会向 TC(Transaction Coordinator,事务协调器) 注册一个分支事务。当请求链路调用完成后,发起方通知 TC 提交或回滚分布式事务,进入二阶段调用流程。此时,TC 会根据之前注册的分支事务回调到对应参与者去执行对应资源的第二阶段。TC 是怎么找到分支事务与资源的对应关系呢?每个资源都有一个全局唯一的资源 ID,并且在初始化时用该 ID 向 TC 注册资源。在运行时,每个分支事务的注册都会带上其资源 ID。这样 TC 就能在二阶段调用时正确找到对应的资源。
这就是我们的 AT 模式。简单总结一下,就是把每个数据库当做一个 Resource,在本地事务提交时会去注册一个分支事务。
这种模式是对业务零入侵,并发没那么高。
TCC 模式
TCC 模型是把锁的粒度完全交给业务处理,它需要每个子事务业务都实现Try-Confirm / Cancel 接口。
TCC 模式本质也是 2PC ,只是 TCC 在应用层控制。
- Try:
- 尝试执行业务
- 完成所有业务检查(一致性)
- 预留必须业务资源(准隔离性)
- Confirm:
- 确认执行业务;
- 真正执行业务,不作任何业务检查
- 只使用Try阶段预留的业务资源
- Confirm 操作满足幂等性
- Cancel:
- 取消执行业务
- 释放Try阶段预留的业务资源
- Cancel操作满足幂等性
这三个阶段,都会按本地事务的方式执行。不同于 XA 的 prepare ,TCC 无需将 XA 的投票期间的所有资源挂起,因此极大的提高了吞吐量。
那么对应到 TCC 模式里,也是一样的,Seata 框架把每组 TCC 接口当做一个 Resource,称为 TCC Resource。这套 TCC 接口可以是 RPC,也以是服务内 JVM 调用。在业务启动时,Seata 框架会自动扫描识别到 TCC 接口的调用方和发布方。如果是 RPC 的话,就是 sofa:reference、sofa:service、dubbo:reference、dubbo:service 等。
扫描到 TCC 接口的调用方和发布方之后。如果是发布方,会在业务启动时向 TC 注册 TCC Resource,与 DataSource Resource 一样,每个资源也会带有一个资源 ID。
如果是调用方,Seata 框架会给调用方加上切面,与 AT 模式一样,在运行时,该切面会拦截所有对 TCC 接口的调用。每调用一次 Try 接口,切面会先向 TC 注册一个分支事务,然后才去执行原来的 RPC 调用。当请求链路调用完成后,TC 通过分支事务的资源 ID 回调到正确的参与者去执行对应 TCC 资源的 Confirm 或 Cancel 方法。
在讲完了整个框架模型以后,大家可能会问 TCC 三个接口怎么实现。因为框架本身很简单,主要是扫描 TCC 接口,注册资源,拦截接口调用,注册分支事务,最后回调二阶段接口。最核心的实际上是 TCC 接口的实现逻辑。下面我将根据蚂蚁金服内部多年的实践来为大家分析怎么实现一个完备的 TCC 接口。
运行 Demo
下面是 dubbo 的例子,运行后报错可以看到回滚信息:
INFO [rpcDispatch_RMROLE_4_8] - onMessage:xid=10.116.22.63:8091:2016481020,branchId=2016481022,branchType=AT,resourceId=jdbc:mysql://localhost:3306/fescar_demo?serverTimezone=UTC,applicationData=null |
注意:
- 数据库驱动与 Mysql 版本一致
- 数据库 rul 添加时区
jdbc.account.url=jdbc:mysql://localhost:3306/fescar_demo?serverTimezone=UTC