文中文件资源和项目在结尾都有资源链接
Seata分为三大模块,分别是TM、RM和TC
TC(TransactionCoordinator)-事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM(TransactionManager)-事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM(ResourceManager)-资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
在Seata中,分布式事务的执行流程:
TM和RM是作为Seata的客户端与业务系统集成在一起,TC作为Seata的服务端独立部署。
服务端存储模式支持三种:
file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)
DB:高可用模式,全局事务会话信息通过DB共享,相对性能差一些
redis:Seata-Server1.3及以上版本支持,性能较高,存在事务信息丢失风险,需要配合实际场景使用
这里我们使用DB高可用模式,找到conf/file.conf文件
修改以上中的信息,找到对应的db配置,修改其中的jdbc连接,要注意其中涉及到三个表(global_table,branch_table,lock_table),同时mysql5和mysql8的驱动是不一样的
mysql5:com.mysql.jdbc.Driver
mysql8:com.mysql.cj.jdbc.Driver
global_table:全局事务表,每当有一个全局事务发起后,就会在该表中记录全局事务的ID
branch_table:分支事务表,记录每一个分支事务的ID,分支事务操作的哪个数据库等信息
lock_table:全局锁
当上述配置好以后,重启Seata即可生效。
Seata支持注册服务到Nacos,以及支持Seata所有配置放到Nacos配置中心,在Nacos中统一维护;在高可用模式下就需要配合Nacos来完成
首先找到conf/registry.conf,修改registry信息
registry{#file、nacos、eureka、redis、zk、consul、etcd3、sofatype="nacos"nacos{application="seata-server"#这里的配置要和客户端保持一致serverAddr="127.0.0.1:8848"group="SEATA_GROUP"#这里的配置要和客户端保持一致namespace=""cluster="default"username="nacos"password="nacos"}config{#file、nacos、apollo、zk、consul、etcd3type="nacos"nacos{serverAddr="127.0.0.1:8848"namespace=""group="SEATA_GROUP"username="nacos"password="nacos"dataId="seataServer.properties"}......}修改好后,将seata中的一些配置上传到Nacos中,因为配置项比较多,所以官方提供了一个config.txt,只下载并且修改其中某些参数后,上传到Nacos中即可。
修改项如下:
service.vgroupMapping.mygroup=default#事务分组store.mode=dbstore.db.driverClassName=com.mysql.cj.jdbc.Driverstore.db.url=jdbc:mysql://127.0.0.1:3306/seatauseUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaistore.db.user=rootstore.db.password=123456修改好这个文件以后,把这个文件放到seata目录下
把这些配置都加入到Nacos配置中,要借助一个脚本来进行执行,官方已经提供好。
新建一个nacos-config.sh文件,将脚本内容复制进去,修改congfig.txt的路径
上述文件修改好后,打开git工具,将nacos-config.sh工具拖拽到窗体中即可或者使用命令
shnacos-config.sh-h127.0.0.1-p8848-gSEATA_GROUP-t88b8f583-43f9-4272-bd46-78a9f89c56e8-unacos-wnacos
-h:nacos地址
-p:端口,默认8848
-g:seata的服务列表分组名称
-t:nacos命名空间id
-u和-w:nacos的用户名和密码
最后会有四个执行失败,是因为redis报错的关系,这个可以忽略,不影响正常使用。最后可以看到在Nacos中有很多的配置项,说明导入成功。再重新其中seata,成功监听到8091端口,表示前置工作都已经准备完成。
Seata定义了全局事务的框架,主要分为以下几步
Seata的全局事务处理过程,分为两个阶段:
Seata的所谓事务模式是指:运行在Seata全局事务框架下的分支事务的行为模式。准确地讲,应该叫作分支事务模式。
不同的事务模式区别在于分支事务使用不同的方式达到全局事务两个阶段的目标。即,回答以下两个问题:
我们以AT模式为例:
接下来就进入重头戏,Seata四大模式的介绍。
Seata1.2.0版本发布了新的事务模型:XA模式,实现了对XA协议的支持。对于XA模式我们需要从三个点去解析它。
XA模式属于两阶段提交。
第一阶段进行事务注册,将事务注册到TC中,执行SQL语句。
第二阶段TC判断无事务出错,通知所有事务提交,否则回滚。
在第一到第二阶段过程中,事务一直占有数据库锁,因此性能比较低,但是所有事务要么一起提交,要么一起回滚,所以能实现强一致性。
无论是AT模式、TCC还是SAGA,这些模式的提出,都是源于XA规范对某些业务场景无法满足
XA规范是X/OPEN组织定义的分布式事务处理(DTP,DistributedTransactionProcessing)标准,XA规范描述了全局事务管理器和局部资源管理器之间的接口,XA规范的目的是允许多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使ACID属性跨越应用程序而保持有效。
XA规范使用两阶段提交(2PC,Two-PhaseCommit)来保证所有资源同时提交或回滚任何特定的事务。因为XA规范最早被提出,所以几乎所有的主流数据库都保有对XA规范的支持。
分布式事务DTP模型定义的角色如下:
DTP模式定义TM和RM之间通讯的接口规范叫XA,简单理解为数据库提供的2PC接口协议,基于数据库的XA协议来实现的2PC又称为XA方案。
现在有应用程序(AP)持有订单库和库存库,应用程序(AP)通过TM通知订单库(RM)和库存库(RM),进行扣减库存和生成订单,这个时候RM并没有提交事务,而且锁定资源。
当TM收到执行消息,如果有一方RM执行失败,分别向其他RM也发送回滚事务,回滚完毕,释放锁资源
当TM收到执行消息,RM全部成功,向所有RM发起提交事务,提交完毕,释放锁资源。
分布式通信协议XA规范,具体执行流程如下所示:
第一步:AP创建了RM1,RM2的JDBC连接。
第二步:AP通知生成全局事物ID,并把RM1,RM2注册到全局事务ID
第三步:执行二阶段协议中的第一阶段prepare
第四步:根据prepare请求,决定整体提交或回滚。
但是对于XA而言,如果一个参与全局事务的资源“失联”了,那么就意味着TM收不到分支事务结束的命令,那么它锁定的数据,将会一直被锁定,从而产生死锁,这个也是Seata需要重点解决的问题。
在Seata定义的分布式事务架构中,利用事务资源(数据局、消息)等对XA协议进行支持,用XA协议的机制来管理分支事务。
执行阶段:
完成阶段:
Seata已经支持了三大事务模式:AT\TCC\SAGA,这三个都是补偿型事务,补偿型事务处理你机制构建在事务资源之上(要么中间件层面,要么应用层),事务资源本身对于分布式的事务是无感知的,这种对于分布式事务的无感知存在有一个根本性的问题,无法做到真正的全局一致性。
例如一个库存记录,在补偿型事务处理过程中,用80扣减为60,这个时候仓库管理员查询数据结果,看到的是60,之后因为异常回滚,库存回滚到原来的80,那么这个时候库存管理员看到的60,其实就是脏数据,而这个中间状态就是补偿型事务存在的脏数据。
和补偿型事务不同,XA协议要求事务资源本身提供对规范和协议的支持,因为事务资源感知并参与分布式事务处理过程中,所以事务资源可以保证从任意视角对数据的访问有效隔离性,满足全局数据的一致性。
项目名:seata-samples
业务开始:business-xa库存服务:stock-xa订单服务:order-xa账号服务:account-xa
把这个项目案例下载下来以后,找到项目名为seata-xa的目录,里面有测试数据库的链接,如果不想用测试数据库,只需要修改官方文档中数据库配置信息即可。
@GlobalTransactionalpublicvoidpurchase(StringuserId,StringcommodityCode,intorderCount,booleanrollback){Stringxid=RootContext.getXID();LOGGER.info("NewTransactionBegins:"+xid);//调用库存减库存Stringresult=stockFeignClient.deduct(commodityCode,orderCount);if(!SUCCESS.equals(result)){thrownewRuntimeException("库存服务调用失败,事务回滚!");}//生成订单result=orderFeignClient.create(userId,commodityCode,orderCount);if(!SUCCESS.equals(result)){thrownewRuntimeException("订单服务调用失败,事务回滚!");}if(rollback){thrownewRuntimeException("Forcerollback...");}}其实现方法较之前差不多,我们只需要在order-xa里面(OrderService.create),添加人为错误(inti=1/0;)
publicvoidcreate(StringuserId,StringcommodityCode,Integercount){Stringxid=RootContext.getXID();LOGGER.info("createorderintransaction:"+xid);inti=1/0;//定单总价=订购数量(count)*商品单价(100)intorderMoney=count*100;//生成订单jdbcTemplate.update("insertorder_tbl(user_id,commodity_code,count,money)values(,,,)",newObject[]{userId,commodityCode,count,orderMoney});//调用账户余额扣减Stringresult=accountFeignClient.reduce(userId,orderMoney);if(!SUCCESS.equals(result)){thrownewRuntimeException("FailedtocallAccountService.");}}里面有一个方法可以进行XA模式和AT模式的转换OrderXADataSourceConfiguration.dataSource
我们可以其中报错,然后再去看对应数据库的数据,没有发生更改,说明我们的XA模式生效了,当你dubug去看里面的库存服务的时候,当操作数据更改的时候,数据库里面其实也是没有记录的,因为XA是强一致性,只有当事务结束完成以后,才会更改其中的数据。
XA模式的加入,补齐了Seata在全局一致性场景下的缺口,形成了AT、TCC、Saga、XA四大事务模式的版图,基本满足了所有场景分布式事务处理的需求。
其中XA和AT是无业务侵入的,而TCC和Saga是有一定业务侵入的。
两阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
AT模式主要特点
在一阶段中,Seata会拦截业务SQL,首先解析SQL语义,找到要操作的业务数据,在数据被操作前,保存下来记录undolog,然后执行业务SQL更新数据,更新之后再次保存数据redolog,最后生成行锁,这些操作都在本地数据库事务内完成,这样保证了一阶段的原子性。
相对一阶段,二阶段比较简单,负责整体的回滚和提交,如果之前的一阶段中有本地事务没有通过,那么就执行全局回滚,否在执行全局提交,回滚用到的就是一阶段记录的undoLog,通过回滚记录生成反向更新SQL并执行,以完成分支的回滚。当然事务完成后会释放所有资源和删除所有日志。
AT流程分为两阶段,主要逻辑全部在第一阶段,第二阶段主要做回滚或日志清理的工作。流程如下:
从上图中我们可以看到,订单服务中TM向TC申请开启一个全局事务,一般通过@GlobalTransactional标注开启,TC会返回一个全局事务ID(XID),订单服务在执行本地事务之前,RM会先向TC注册一个分支事务,订单服务依次生成undolog执行本地事务,生成redolog提交本地事务,向TC汇报,事务执行OK。
订单服务发起远程调用,将事务ID传递给库存服务,库存服务在执行本地事务之前,先向TC注册分支事务,库存服务同样生成undoLog和redoLog,向TC汇报,事务状态成功。
如果正常全局提交,TC通知RM一步清理掉本地undo和redo日志,如果存在一个服务执行失败,那么发起回滚请求。通过undolog进行回滚。
这个时候RM会用redolog进行验证,对比数据是否一样,从而得知数据是否有别的事务进行修改过,undolog是用于被修改前的数据,可以用来回滚,redolog是用于被修改后的数据,用于回滚校验。
如果数据没有被其他事务修改过,可以直接进行回滚,如果是脏数据,redolog校验后进行处理。
了解了AT模型的基本操作,接下来就来实战操作一下,关于AT模型具体是如何实现的。首先设计两个服务cloud-alibaba-seata-order和cloud-alibaba-seata-stock
表结构t_order、t_stock和undo_log三张表,项目源码和表结构,加上undo_log表,此表用于数据的回滚,文末有链接。
cloud-alibaba-seata-order核心代码如下:
controller
@RestControllerpublicclassOrderController{@AutowiredprivateOrderServiceorderService;@GetMapping("order/create")@GlobalTransactional//开启分布式事务publicStringcreate(){orderService.create();return"订单创建成功!";}}OrderService
publicinterfaceOrderService{voidcreate();}StockClient
@FeignClient(value="seata-stock")publicinterfaceStockClient{@GetMapping("/stock/reduce")Stringreduce();}OrderServiceImpl
@ServicepublicclassOrderServiceImplimplementsOrderService{@AutowiredprivateOrderMapperorderMapper;@AutowiredprivateStockClientstockClient;@Overridepublicvoidcreate(){//扣减库存stockClient.reduce();System.out.println("扣减库存成功!");//手工异常用于回滚库存信息inti=1/0;System.err.println("异常!");//创建订单orderMapper.createOrder();System.out.println("创建订单成功!");}}OrderMapper
@MapperpublicinterfaceOrderMapper{@Insert("insertintot_order(order_no,order_num)value(order_no+1,1)")voidcreateOrder();}cloud-alibaba-seata-stock核心代码如下:
但是当我们F9通过以后,库存数量恢复,并且undo_log表的数据行也没有了,这个时候证明我们的Seata事务生效,回滚成功。
由于Seata需要在不同的服务之间传递全局唯一的事务ID,和Dubbo等框架集成会比较友好,例如Dubbo可以用过隐士传参来进行事务ID的传递,整个事务ID的传播过程对开发者也可以做到无感知。
TCC是分布式事务中的二阶段提交协议,它的全称为Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下:
TCC是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是TCC完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。
AT模式基于支持本地ACID事务的关系型数据库:
相应的,TCC模式,不依赖于底层数据资源的事务支持:
所谓TCC模式,是指支持把自定义的分支事务纳入到全局事务的管理中。
特点:
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务(执行处理时候出错了,给一个修复的机会)都由业务开发实现。
Saga模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga模式是一种长事务解决方案。
之前我们学习的Seata分布式三种操作模型中所使用的的微服务全部可以根据开发者的需求进行修改,但是在一些特殊环境下,比如老系统,封闭的系统(无法修改,同时没有任何分布式事务引入),那么AT、XA、TCC模型将全部不能使用,为了解决这样的问题,才引用了Saga模型。
比如:事务参与者可能是其他公司的服务或者是遗留系统,无法改造,可以使用Saga模式。
Saga模式是Seata提供的长事务解决方案,提供了异构系统的事务统一处理模型。在Saga模式中,所有的子业务都不在直接参与整体事务的处理(只负责本地事务的处理),而是全部交由了最终调用端来负责实现,而在进行总业务逻辑处理时,在某一个子业务出现问题时,则自动补偿全面已经成功的其他参与者,这样一阶段的正向服务调用和二阶段的服务补偿处理全部由总业务开发实现。
目前Seata提供的Saga模式只能通过状态机引擎来实现,需要开发者手工的进行Saga业务流程绘制,并且将其转换为Json配置文件,而后在程序运行时,将依据子配置文件实现业务处理以及服务补偿处理,而要想进行Saga状态图的绘制,一般需要通过Saga状态机来实现。
基本原理:
Saga状态机的应用
总的来说在Seata的中AT模式基本可以满足百分之80的分布式事务的业务需求,AT模式实现的是最终一致性,所以可能存在中间状态,而XA模式实现的强一致性,所以效率较低一点,而Saga可以用来处理不同开发语言之间的分布式事务,所以关于分布式事务的四大模型,基本可以满足所有的业务场景,其中XA和AT没有业务侵入性,而Saga和TCC具有一定的业务侵入。
关于资料和项目源码,公众号后台:seata,即可获取
我是牧小农怕什么真理无穷,进一步有进一步的欢喜,大家加油!