当前位置: 首页 > 图灵资讯 > 技术篇> 初识Seata

初识Seata

来源:图灵教育
时间:2023-10-29 14:06:12

三、初识Seata

Seata是的 2019 年 1 蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于为用户提供高性能、简单易用的分布式服务,打造一站式的分布式解决方案。

官网地址:http://seata.io/,在文档和播客中提供了大量的使用说明和源代码分析。

image-20210724172225817

3.1.Seata的架构

Seata事务管理有三个重要角色:

  • TC (Transaction Coordinator) - **事务协调员:**维护全局和分支事务状态,协调全局事务提交或回滚。

  • TM (Transaction Manager) - **事务管理器:**定义全局事务范围,开始全局事务,提交或回滚全局事务。

  • RM (Resource Manager) - **资源管理器:**管理处理分支事务的资源,与TC谈判,注册分支事务,报告分支事务状态,推动分支事务提交或回滚。

整体架构如图所示:

image-20210724172326452

基于上述结构,Seata提供了四种不同的分布式事务解决方案:

  • XA模式:分阶段事务模式的强一致性,牺牲了一定的可用性,没有业务入侵
  • TCC模式:最终一致的分阶段业务模式,业务入侵
  • AT模式:最终一致的分阶段业务模式,无业务入侵,也是Seata的默认模式
  • SAGA模式:长期业务模式,业务入侵

无论哪种方案,都离不开TC,也就是事务的协调者。

3.2.部署TC服务

参考博主上一篇文章的部署,链接地址

3.3.Seatatatata集成微服务集成

以order-service为例。

3.3.1.引入依赖

首先,在order-service中引入依赖:

<!--seata--><dependency>    <groupId>com.alibaba.cloud</groupId>    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>    <exclusions>        <!--版本较低,1.3.0,因此排除-->         <exclusion>            <artifactId>seata-spring-boot-starter</artifactId>            <groupId>io.seata</groupId>        </exclusion>    </exclusions></dependency><dependency>    <groupId>io.seata</groupId>    <artifactId>seata-spring-boot-starter</artifactId>    <!--seata starter 采用1.4.2版本-->    <version>${seata.version}</version></dependency>
3.3.2.配置TC地址

aplication在order-service中.在yml中,配置TC服务信息,通过注册中心nacos,结合服务名称获取TC地址:

seata:  registry: # 配置TC服务注册中心,根据这些信息,微服务到注册中心获取TC服务地址    type: nacos # 注册中心类型 nacos    nacos:      server-addr: 127.0.0.1:8848 # nacos地址      namespace: "" # namespace,默认为空      group: DEFAULT_GROUP # 分组,默认为DEFAULT_GROUP      application: seata-tc-server # seata服务名称      username: nacos      password: nacos  tx-service-group: seata-demo # 事务组名称  service:    vgroup-mapping: # 事务组与cluster的映射关系      seata-demo: SH

根据这些配置,微服务如何找到TC地址?

在Nacos中注册微服务,需要四个信息来确定一个具体的例子:

  • namespace:命名空间
  • group:分组
  • application:服务名
  • cluster:集群名

以上四个信息可以在刚才的yaml文件中找到:

image-20210724173654258

namespace是空的,是默认的publice

TC服务的信息相结合:public@DEFAULT_GROUP@seata-tc-server@SH,这样,TC服务集群就可以确定了。然后你可以去Nacos获取相应的实例信息。

3.3.3.其它服务

另外两个微服务也参考order-service的步骤,完全一样。

4.动手实践

让我们一起学习Seata中的四种不同的事务模式。

4.1.XA模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 它描述了TM与局部RM之间的接口,几乎所有主流数据库都是对的 XA 规范 提供支持。

4.1.1.两阶段提交

XA是标准。目前,主流数据库已经实现了这一标准,实现的原则是基于两个阶段的提交。

正常情况:

image-20210724174102768

异常情况:

image-20210724174234987

一阶段:

  • 事务协调员通知每个事务参与者执行本地事务
  • 当地事务执行完成后,向事务协调员报告事务执行状态。此时,如果事务未提交,继续持有数据库锁

二阶段:

  • 事务协调员根据第一阶段的报告判断下一步的操作
    • 一阶段成功的,通知所有事务参与者提交事务
    • 任何一个参与者在第一阶段失败的,通知所有事务参与者回滚事务
4.1.2.XA模型Seata

为了适应自己的事务模型,Seata对原有的XA模式进行了简单的包装和改造,基本结构如图所示:

image-20210724174424070

RM一阶段工作:

① 向TC注册分支机构

② 执行sql分支业务,但不提交

③ 报告执行到TC

TC二阶段工作:

  • TC检测各分支事务的执行状态

    a.如果一切成功,通知所有RM提交事务

    b.如有失败,通知所有RM回滚事务

RM二阶段工作:

  • 接收TC指令,提交或回滚事务
4.1.3.优缺点

XA模式的优点是什么?

  • 符合ACID原则的事务强一致性。
  • 支持常用数据库,实现简单,没有代码入侵

XA模式的缺点是什么?

  • 由于第一阶段需要锁定数据库资源,直到第二阶段结束才释放,性能差
  • 依靠关系数据库实现事务
4.1.4.实现XA模式

Seata的Starter已经完成了XA模式的自动组装,步骤如下:

修改application).开启XA模式的yml文件(每个参与事务的微服务):

seata:  data-source-proxy-mode: XA

2)添加@Globaltransactional注释发起全局事务的入口方法:

本例是OrderServiceimpl中的create方法.

image-20210724174859556

3)重启服务并进行测试

重启order-service,再次测试,发现三个微服务无论如何都能成功回滚。

4.2.AT模式

AT模型也是分阶段提交的事务模型,但缺陷弥补了XA模型中资源锁定周期过长的缺陷。

4.2.1.SeataAT模型

基本流程图:

image-20210724175327511

阶段一RM工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

提交阶段二时RM的工作:

  • 删除undo-log即可

RM在第二阶段回滚时的工作:

  • 在更新之前,根据undo-log恢复数据
4.2.2.流程梳理

我们用真正的业务来梳理AT模式的原理。

例如,记录用户余额的另一个数据库表:

idmoney1100

SQL的分支业务之一是:

update tb_account set money = money - 10 where id = 1

在AT模式下,当前分支的执行流程如下:

一阶段:

1)TM发起并向TC注册全局事务

2)TM调用分支事务

3)分支业务准备执行SQL业务

4)RM拦截业务SQL,根据where条件查询原始数据,形成快照。

{    "id": 1, "money": 100}

5)RM执行SQL业务,提交本地事务,释放数据库锁。 money = 90

6)RM向TC报告当地事务状态

二阶段:

1)TM通知TC事务结束

2)TC检查分支的事务状态

a)如果都成功了,立即删除快照

b)如果分支失败,需要回滚。阅读快照数据({"id": 1, "money": 100}),将快照恢复到数据库。此时,数据库再次恢复到100

流程图:

image-20210724180722921

4.2.3.AT和XA的区别

AT模式和XA模式最大的区别是什么?

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依靠数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强烈一致;AT模式最终一致;
4.2.4.脏写问题

多线程并发访问AT模式的分布式事务时,可能会出现脏写问题,如图所示:

image-20210724181541234

解决方案是引入全局锁的概念。在释放DB锁之前,先获得全局锁。避免同时操作当前数据的另一项事务。

image-20210724181843029

4.2.5.优缺点

AT模式的优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能更好
  • 利用全局锁实现读写隔离
  • 无代码入侵,框架自动完成回滚和提交

AT模式的缺点:

  • 两个阶段属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比XA模式好得多
4.2.6.实现AT模式

AT模式中的快照生成、回滚等动作由框架自动完成,无代码入侵,因此实现非常简单。

然而,AT模式需要一个表来记录整体锁,另一个表来记录数据快照undo_log。

1)导入数据库表,记录全局锁定

导入提供的SQL文件:seata-at.sql,lock_table导入TC服务相关数据库,undo_log表导入与微服务相关的数据库:

https://aliyun.chengke.net/resource/sqlFile/account_freeze_tbl.sql

https://aliyun.chengke.net/resource/sqlFile/seata-at.sql

https://aliyun.chengke.net/resource/sqlFile/seata-demo.sql

https://aliyun.chengke.net/resource/sqlFile/seata-saga.sql

https://aliyun.chengke.net/resource/sqlFile/seata-tc-server.sql

image-20210724182217272

2)修改application.yml文件可以将事务模式修改为AT模式:

seata:  data-source-proxy-mode: AT # 默认是AT

3)重启服务并进行测试

4.3.TCC模式

TCC模式与AT模式非常相似,每个阶段都是独立的。不同之处在于,TCC通过人工编码实现数据恢复。有三种方法可以实现:

  • Try:检测和预留资源;

  • Confirm:完成资源运营业务;要求 Try 成功 Confirm 一定要成功。

  • Cancel:预留资源的释放可以理解为try的反向操作。

4.3.1.流程分析

例如,一个扣除用户余额的业务。假设账户A的原始余额为100元,则需要扣除30元。

  • 阶段一( Try ):检查余额是否充足,冻结金额增加30元,可扣除30元

初识余额:

image-20210724182424907

余额充足,可冻结:

image-20210724182457951

此时,总金额 = 冻结金额 + 可用金额,数量仍为100不变。直接提交事务不需要等待其他事务。

  • 阶段二(Confirm):假如要提交(Confirm),冻结金额扣除30

确认可以提交,但之前可用金额已经扣除,这里只需清除冻结金额即可:

image-20210724182706011

此时,总金额 = 冻结金额 + 可用金额 = 0 + 70 = 70元

  • 阶段二(Canncel):如果要回滚(Cancel),冻结金额扣除30,可用余额增加30

如需回滚,则需释放冻结金额,恢复可用金额:

image-20210724182810734

4.3.2.TCC模型Seata

Seata中的TCC模型仍然延续以前的事务架构,如图所示:

image-20210724182937713

4.3.3.优缺点

TCC模式的每个阶段都在做什么?

  • Try:检查和预留资源
  • Confirm:业务执行和提交
  • Cancel:释放预留资源

TCC的优点是什么?

  • 一阶段完成直接提交事务,释放数据库资源,性能良好
  • 与AT模型相比,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可用于非事务数据库

TCC的缺点是什么?

  • 有代码入侵,需要人工编写try、Confirm和Cancel接口太麻烦了
  • 软状态,事务是最终一致的
  • 要考虑Confirm和Cancel的失败,做好权力处理
4.3.4.事务悬架和空回滚1)空回滚1

当分支事务的try阶段被阻塞时,可能会导致整体事务加班,触发第二阶段的cancel操作。cancel操作在未执行try操作时首先执行。此时,cancel无法回滚,即空回滚。

如图:

image-20210724183426891

在执行cancel操作时,应判断try是否已经执行,如果没有执行,则应空回滚。

2)业务悬挂

对于已经空回滚的业务,之前被阻塞的try操作恢复并继续执行try,confirm或cancel将永远不可能。 ,业务一直处于中间状态,这就是业务悬挂。

在执行try操作时,应判断cancel是否已经执行。如果已经执行,应防止空回滚后的try操作,避免悬挂

4.3.5.实现TCC模式

为了解决空气回滚和业务悬挂的问题,有必要记录当前的业务状态,即在try、还是cancel?

1)思路分析

在这里,我们定义一张表:

CREATE TABLE `account_freeze_tbl` (  `xid` varchar(128) NOT NULL,  `user_id` varchar(255) DEFAULT NULL COMMENT 用户id,  `freeze_money` int(11) unsigned DEFAULT '0' COMMENT "冻结金额",  `state` int(1) DEFAULT NULL COMMENT 事务状态,0:try,1:confirm,2:cancel',  PRIMARY KEY (`xid`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

其中:

  • xid:是全局事务id
  • freeze_money:用来记录用户冻结的金额
  • state:用于记录事务状态

那时,我们的业务开业怎么办?

  • Try业务:
    • 记录冻结金额和事务状态到account_freeze表格
    • 扣除account表的可用金额
  • Confirm业务
    • 根据xid删除account_freeze表的冻结记录
  • Cancel业务
    • 修改account_freeze表格,冻结金额为0,state为2
    • 修改account表,恢复可用金额
  • 如何判断是否空回滚?
    • 在cancel业务中,根据xid查询account_freeze,如果是null,说明try还没有做,需要空回滚
  • 如何避免业务悬挂?
    • 在try业务中,根据xid查询account_freeze ,如果已经存在,证明Cancel已经执行,拒绝执行try业务

接下来,我们将改造account-service,利用TCC实现余额扣除功能。

2)声明TCC接口

TCC的Try、Confirm、Cancel方法需要在界面中基于注释进行声明,

我们在account-service项目中cn.itcast.account.service包中新建一个接口,声明TCC三个接口:

package cn.itcast.account.service;import io.seata.rm.tcc.api.BusinessActionContext;import io.seata.rm.tcc.api.BusinessActionContextParameter;import io.seata.rm.tcc.api.LocalTCC;import io.seata.rm.tcc.api.TwoPhaseBusinessAction;@LocalTCCpublic interface AccountTCCService {    @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")    void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,                @BusinessActionContextParameter(paramName = "money")int money);    boolean confirm(BusinessActionContext ctx);    boolean cancel(BusinessActionContext ctx);}
3)编写实现类

在account-service服务中cn.itcast.account.service.impl包下新建一类,实现TCC业务:

package cn.itcast.account.service.impl;import cn.itcast.account.entity.AccountFreeze;import cn.itcast.account.mapper.AccountFreezeMapper;import cn.itcast.account.mapper.AccountMapper;import cn.itcast.account.service.AccountTCCService;import io.seata.core.context.RootContext;import io.seata.rm.tcc.api.BusinessActionContext;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Service@Slf4jpublic class AccountTCCServiceImpl implements AccountTCCService {    @Autowired    private AccountMapper accountMapper;    @Autowired    private AccountFreezeMapper freezeMapper;    @Override    @Transactional    public void deduct(String userId, int money) {        // 0.获取事务id        String xid = RootContext.getXID();        // 1.扣除可用余额        accountMapper.deduct(userId, money);        // 2.记录冻结金额,事务状态        AccountFreeze freeze = new AccountFreeze();        freeze.setUserId(userId);        freeze.setFreezeMoney(money);        freeze.setState(AccountFreeze.State.TRY);        freeze.setXid(xid);        freezeMapper.insert(freeze);    }    @Override    public boolean confirm(BusinessActionContext ctx) {        // 1.获取事务ID        String xid = ctx.getXid();        // 2.根据id删除冻结记录。        int count = freezeMapper.deleteById(xid);        return count == 1;    }    @Override    public boolean cancel(BusinessActionContext ctx) {        // 查询冻结记录        String xid = ctx.getXid();        AccountFreeze freeze = freezeMapper.selectById(xid);        // 1.恢复可用余额        accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());        // 2.清零冻结金额,改为CANCEL的状态        freeze.setFreezeMoney(0);        freeze.setState(AccountFreeze.State.CANCEL);        int count = freezeMapper.updateById(freeze);        return count == 1;    }}
4.4.SAGA模式

Saga 模式是 Seata 蚂蚁金服将为即将开源的长期事务解决方案做出主要贡献。

其理论基础是Hector & Kenneth Sagas于1987年发表。

Seata官网Saga指南:https://seata.io/zh-cn/docs/user/saga.html

4.4.1.原理

在 Saga 在模式下,分布式事务中有多个参与者,每个参与者都是一个积极的补偿服务,用户需要根据业务场景实现其积极操作和反向回滚操作。

在分布式事务的实施过程中,每个参与者依次进行正向操作。如果所有正向操作都成功实施,则提交分布式事务。如果任何正向操作失败,分布式事务将返回执行前参与者的反向回滚操作。已提交的参与者将分布式事务返回初始状态。

image-20210724184846396

Saga也分为两个阶段:

  • 第一阶段:直接提交当地事务
  • 第二阶段:成功是什么都不做;失败是通过编写补偿业务来回滚动的
4.4.2.优缺点

优点:

  • 事务参与者可以根据事件驱动实现异步调用,吞吐量高
  • 一阶段直接提交事务,无锁,性能好
  • 无需在TCC中编写三个阶段,即可实现简单

缺点:

  • 软状态持续时间不确定,时效性差
  • 没有锁,没有事务隔离,会有脏写
4.5.比较四种模式

从以下几个方面对四个实现进行比较:

  • 一致性:能保证事务的一致性吗?强一致性还是最终一致性?
  • 隔离:事务之间的隔离是什么?
  • 代码入侵:是否需要对业务代码进行改造?
  • 性能:是否有性能损失?
  • 场景:常见的商业场景

如图:

image-20210724185021819

5.高可用

作为分布式事务的核心,Seata的TC服务必须保证集群的高可用性。

5.1.高可用架构模型

建立TC服务集群非常简单,可以启动多个TC服务,注册到nacos。

但是,集群不能保证100%的安全。如果集群所在机房出现故障怎么办?因此,如果要求较高,一般会在不同的地方做多个机房容灾。

比如上海一个TC集群,杭州另一个TC集群:

image-20210724185240957

基于事务组的微服务(tx-service-group)与TC集群的映射关系,找出目前应该使用哪个TC集群。当SH集群出现故障时,只需将Vgroup-maping中的映射关系更改为HZ。所有微服务都将切换到HZ的TC集群。

5.2.实现高可用

具体实现请参考:链接地址

第三章节:

image-20210724185638729

包括高质量开源项目在内的博主个人开源博客地址: https://www.chengke.net