分布式事务协调组件解析

前期也是基于Tcc的思想,将Config和Cancel按标准做成自动化执行,这样形成了无业务侵入性

后期我司制作的Poseidon通信框架结合,形成开发中无缝的微服务分布式事务协调器

而在三月初看到了Fescar技术的开源,就对其进行了解,思想大致与Tcc自动化改造等同

基本概述

fescar分布式事务

1.组件

Transaction Coordinator (TC)

事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

Transaction Manager (TM):

控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

Resource Manager (RM):

控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。


2.一个典型的分布式事务过程

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  2. XID 在微服务调用链路的上下文中传播。
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
  4. TM 向 TC 发起针对 XID 的全局提交或回滚决议。
  5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。


3.fescar锁设计

Fescar一阶段

  1. 本地(Branch)在向TC注册的时候,把本地事务需要修改的数据table+pks提交到server端申请锁,拿到全局锁后,才能提交本地事务
  2. 全局锁的结构:resourceId + table + pks
  3. 锁是存在server端 branchSession中

Fescar二阶段

一阶段本地事务提交,db的锁释放了(for update锁),但是全局锁继续保持,
直到二阶段决议(注意释放锁的顺序):

  1. 提交:TC 释放锁,通知branch提交后 (rm端异步处理)
  2. 回滚:TC 通知branch回滚后,释放锁(rm端同步处理 执行undo_log)

 

分支事务执行顺序图
 

 

Fescar中 RM TM TC工作流程图

Fescar全局锁简单总结

操作一条记录的分支事务,必须等待这条记录的前一个分支事务执行结束(具体commit rollback情况分析如下),才能持有锁。
其实相比XA的锁,fescar在每个分支事务的一阶段结束后都释放了db的锁,所以fescar的性能瓶颈应该在于二阶段的执行速度(释放锁的快慢)
因为分布式事务在执行事务编排前,一般会校验业务的正确性,所以发生回滚的概率相对较低,所以先考虑二阶段commit操作。

场景分析

Commit场景分析:
TM通知server进行commit,server立马释 branch的锁,然后再逐个通知RM提交
消耗:1 rpc操作,(branch删除undo_log放在异步队列里面做)

Rollback场景分析:
TM通知server进行rollback,server通知RM回滚后立马释放 branch的锁。
消耗:1 + N的rpc操作 + N的回滚sql操作

写隔离

  1. 一阶段本地事务提交前,需要确保先拿到 全局锁 。
  2. 拿不到 全局锁 ,不能提交本地事务。
  3. 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

以一个示例来说明:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。

tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Fescar(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Fescar 的方式是通过 SELECT FOR UPDATE 语句的代理。

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Fescar 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

源码解读

设计

客户端

源头来源于GlobalTransactionScanner