分布式事务seata原理

理论基础

CAP指的是一个分布式系统最多只能同时满足一致性(Consistency)可用性(Availability)分区容错性(Partition tolerance)这三项中的两项。

BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(CAP的一致性就是强一致性),但可以采用适当的采取弱一致性,即:最终一致性。

BASE是指基本可用(Basically Available)软状态( Soft State)最终一致性( Eventual Consistency)

BASE理论是柔性事物的理论基础

分布式事务常用方案

2PC/3PC:依赖于数据库,能够很好的提供强一致性和强事务性,但延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。

TCC:适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如:互联网金融企业最核心的三个服务:交易、支付、账务。

Saga:由于Saga事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。Saga由于缺少预提交动作,导致补偿动作的实现比较麻烦,例如:业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。所以,Saga事务较适用于补偿动作容易处理的场景。

最终一致性:适用于事务中参与方支持操作幂等,对一致性要求不高,业务上能容忍数据不一致到一个人工检查周期,事务涉及的参与方、参与环节较少,业务上有对账/校验系统兜底。

seata组成结构

seata组成结构:

  • Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
  • Transaction Manager (TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
  • Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

其中,TM是一个分布式事务的发起者和终结者,TC负责维护分布式事务的运行状态,而RM则负责本地事务的运行。

seata客户端启动流程

1)自动加载Bean属性和配置信息。

2)初始化TM。

3)初始化RM。

4)初始化分布式事务客户端完成,完成代理数据库配置。

5)连接TC(seata服务端),注册RM和TM。

6)开启全局事务。

seata-AT模式(官方推荐模式)

AT模式主要分为两个阶段:

第一阶段

执行流程:

  • 解析SQL,获取SQL类型(CRUD)、表信息、条件(如:where) 等相关信息。
  • 查询前镜像(改变之前的数据),根据解析得到的条件信息,生成查询语句,定位数据。
  • 执行业务SQL,更新数据。
  • 查询后镜像(改变后的数据),根据前镜像的结果,通过主键定位数据。
  • 插入回滚日志,将前后镜像数据以及业务SQL等信息,组织成一条回滚日志记录,插入到Undo Log表中。
  • 提交前,向TC注册分支,申请全局锁。
  • 本地事务提交,业务数据的更新和生成的Undo Log一起提交。
  • 将本地事务提交的结果通知给TC。

第二阶段

如果没问题,执行提交操作:

执行流程:

  • 收到TC分支提交请求,将请求放入到一个异步任务的队列中,返回提交成功的结果给TC。
  • 异步任务阶段的分支提交请求删除Undo Log中记录。

如果有问题,执行回滚操作:

执行流程:

  • 开启本地事务,通过XID和BranchID查找到对应的Undo Log记录。
  • 根据Undo Log中的前镜像和业务SQL的相关信息生成并执行回滚语句。
  • 提交本地事务,将本地事务的执行结果(分支事务回滚的信息)通知给TC。

seata分布式事务执行示例

如图所示:

执行步骤:

1)TM(订单服务)向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID,XID在微服务调用链路的上下文中传播。

2)RM(订单服务)向TC注册分支事务,执行业务SQL和UNDO_LOG数据的插入。在提交本地事务前,RM会向TC申请关于相关记录的全局锁:

  • 如果申请到了相关记录的全局锁,则直接提交本地事务,并向TC汇报本地事务执行成功(此时全局锁并没有释放,全局锁的释放取决于二阶段是提交命令还是回滚命令)。
  • 如果申请不到相关记录的全局锁,则说明有其他事务也在对这条记录进行操作,因此它会在一段时间内重试,重试失败则回滚本地事务,并向TC汇报本地事务执行失败。

3)执行远程服务调用,即:RM(库存服务)向TC注册分支事务,执行业务SQL和UNDO_LOG数据的插入。执行过程与订单服务一致。

4)TC根据所有的分支事务执行结果,向RM下发提交或回滚命令。

5)RM如果收到TC的提交命令,则释放相关记录的全局锁,再将提交请求放入一个异步任务的队列中,返回提交成功的结果给TC。异步队列中的提交请求执行时,只是删除UNDO LOG表中对应的记录。

6)RM如果收到TC的回滚命令,则会开启一个本地事务,通过XID和Branch ID查找到UNDO LOG表中对应的记录。将UNDO LOG中的后镜像数据与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况需要根据配置策略来做处理。否则,根据UNDO LOG中的前镜像数据和业务SQL的相关信息生成并执行回滚的语句并执行,然后提交本地事务达到回滚的目的,最后释放相关记录的全局锁。

UNDO_LOG表中的记录示例

{
    "branchId": 641789253,
    "undoItems": [
        {
            "afterImage": {
                "rows": [
                     {
                        "fields": [
                            {
                            "name": "id",
                            "type": 4,
                            "value": 1
                            }, {
                            "name": "name",
                            "type": 12,
                            "value": "GTS"
                            }, {
                            "name": "since",
                            "type": 12,
                            "value": "2014"
                            }
                        ]
                    }
                ],
                "tableName": "product"
            },
            "beforeImage": {
                "rows": [
                    {
                        "fields": [
                            {
                            "name": "id",
                            "type": 4,
                            "value": 1
                            }, {
                            "name": "name",
                            "type": 12,
                            "value": "TXC"
                            }, {
                            "name": "since",
                            "type": 12,
                            "value": "2014"
                            }
                        ]
                    }
                ],
                "tableName": "product"
            },
            "sqlType": "UPDATE"
        }
    ],
    "xid": "xid:xxx"
}

为什么seata在第一阶段就直接提交了分支事务?

seata能够在第一阶段直接提交事务,是因为seata为每一个RM维护了一张UNDO_LOG表(这张表需要客户端自行创建),其中保存了每一次本地事务的回滚数据。因此,二阶段的回滚并不依赖于本地数据库事务的回滚,而是RM直接读取UNDO_LOG表的记录,并将数据库中的数据更新为UNDO_LOG中存储的历史数据(前镜像数据和业务SQL)。这也是在使用seata作为分布式事务解决方案时,需要在参与分布式事务的每一个服务中加入UNDO_LOG表。

如果第二阶段是提交命令,则RM并不会对数据进行提交(因为第一阶段已经提交),而是发起一个异步请求删除UNDO_LOG中关于本事务的记录。

注意:由于seata在第一阶段直接提交了本地事务,会造成隔离性问题,因此Seata的默认隔离级别为:Read Uncommitted,而seata也支持Read Committed的隔离级别。

seata使用存在的问题

seata事务代理只是代理局部各自的事务,在原生SQL的前后记录了操作的信息,存储在undo_log表中,seata是采用undo_log生成逆向SQL回滚操作。seata的局部事务已经写到了库中,避免了死锁现象,但容易出现脏读的情况(因为seata默认隔离级别为Read Uncommitted)。

seata-Read Committed隔离级别

seata由于一阶段RM自动提交本地事务的原因,默认隔离级别为Read Uncommitted。如果希望隔离级别为Read Committed,那么可以使用SELECT…FOR UPDATE语句。seata引擎重写了SELECT…FOR UPDATE语句执行逻辑,SELECT…FOR UPDATE语句的执行会申请全局锁 ,如果全局锁被其他事务持有,则释放本地锁(回滚SELECT…FOR UPDATE语句的本地执行)并重试。这个过程中,查询会被阻塞,直到拿到全局锁。

seata实现2PC与传统2PC的差别

架构层次:传统2PC方案的RM实际上是在数据库层,RM本质上就是数据库自身,通过XA协议实现,而Seata的RM是以jar包的形式作为中间件层部署在应用程序侧。

两阶段提交:传统2PC无论第二阶段的决议是commit还是rollback,事务性资源的锁都要保持到第二阶段完成才释放。而Seata的做法是在第一阶段就将本地事务提交,这样就可以省去第二阶段持锁的时间,整体提高效率。

seata使用示例

分布式事务seata的使用请查阅本人发布的另外一篇seata使用示例文章。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值