Seata
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。
支持的模式
目前已支持 Dubbo、Spring Cloud、Sofa-RPC、Motan 和 grpc 等RPC框架,其他框架持续集成中。
Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
AT 模式
提供无侵入自动补偿的事务模式,目前已支持 MySQL、 Oracle 、PostgreSQL和 TiDB的AT模式,H2 开发中
TCC 模式
支持 TCC 模式并可与 AT 混用,灵活度更高
SAGA 模式
为长事务提供有效的解决方案
XA 模式(开发中)
支持已实现 XA 接口的数据库的 XA 模式
原理架构
XID:全局唯一的事务ID
TC:Transaction Coordinator,事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或者回滚。
TM:Transaction Manager,控制全局事务的边界,负责启动一个全局事务,并最终发布全局的提交或回滚的决议。
RM:Resource Manager,控制分支事务,负责分支注册,状态汇报,并接受事务协调的指令,驱动分支(本地)事务的提交或回滚。
Seata AT 模式
先讲解AT模式
前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
以下写一个Demo演示。
首先下载Seata-server,文章不给放链接,真的很坑。在Github中https://github.com/后面加上下面的地址,
seata/seata/releases/tag/v0.9.0。
解压,seata-server-0.9.0.zip,解压完成进入conf目录。
将my_test_tx_group修改为自己自定义的名字,在这里我改成nb_tx_group。
然后,将mode="file",改为mode="db"。
修改数据库的url,账号和密码。
打开conf目录下的registry.conf文件,修改type="file",改成type="nacos"。
建立seata库,建立完成后,导入sql脚本。
导入脚本,生成三个表branch_table,globle_table,lock_table。
接着,启动nacos,再启动seata服务端,bin目录下的seata-server.bat。发现启动正常。
再新增三个seata_order订单库,seata_storage库存库,seata_account账户库,用于测试分布式事务。
create database seata_order;
create database seata_storage;
create database seata_account;
每个库建立一个业务表,和一个undo_log。
undo_log为官方需要在业务库建立的一个表。
建表语句见安装包脚本:
建立三个模块:
seata-order-service8801,订单服务
seata-account-service8802,账户服务
seata-stoarage-service8803,库存服务
三个模块添加依赖:
<code><
dependencies
><
dependency
><
groupId
>com.alibaba.cloudgroupId
><
artifactId
>spring-cloud-starter-alibaba-nacos-discoveryartifactId
>dependency
><
dependency
><
groupId
>com.alibaba.cloudgroupId
><
artifactId
>spring-cloud-starter-alibaba-seataartifactId
><
exclusions
><
exclusion
><
artifactId
>seata-allartifactId
><
groupId
>io.seatagroupId
>exclusion
>exclusions
>dependency
><
dependency
><
groupId
> io.seatagroupId
><
artifactId
>seata-allartifactId
><
version
>0.9.0version
>dependency
><
dependency
><
groupId
>org.springframework.cloudgroupId
><
artifactId
>spring-cloud-starter-openfeignartifactId
>dependency
><
dependency
><
groupId
>org.springframework.bootgroupId
><
artifactId
>spring-boot-starter-webartifactId
>dependency
><
dependency
><
groupId
>org.springframework.bootgroupId
><
artifactId
>spring-boot-starter-actuatorartifactId
>dependency
><
dependency
><
groupId
>mysqlgroupId
><
artifactId
>mysql-connector-javaartifactId
><
version
>5.1.37version
>dependency
><
dependency
><
groupId
>com.alibabagroupId
><
artifactId
>druid-spring-boot-starterartifactId
><
version
>1.1.10version
>dependency
><
dependency
><
groupId
>org.mybatis.spring.bootgroupId
><
artifactId
>mybatis-spring-boot-starter
artifactId
><
version
>2.0.0version
>dependency
><
dependency
><
groupId
>org.springframework.bootgroupId
><
artifactId
>spring-boot-starter-testartifactId
><
scope
>testscope
>dependency
><
dependency
><
artifactId
>springcloud-commomartifactId
><
groupId
>com.learn.springcloudgroupId
><
version
>1.0-SNAPSHOTversion
>dependency
>dependencies
>/<code>
配置文件
<code>server
:port
:8801
spring
:application
:name
: seata-order-servicecloud
:alibaba
:seata
:tx-service-group
: nb_tx_groupnacos
:discovery
:server-addr
:localhost
:8848
#配置Nacos地址datasource
:driver-class-name
: com.mysql.jdbc.Driverurl
:jdbc
:mysql
:username
: rootpassword
:feign
:hystrix
:enabled
: falselogging
:level
:io
:seata
: infomybatis
:mapperLocations
:classpath
:mapper/<code>
将刚才配置好的file.conf,register.conf放入resources目录下
其他的配置DataSource配置,dao,service,domain,mybatis的配置xml文件
就不贴出来了。
seata-account-service8802,
seata-stoarage-service8803大体配置也差不多,就不贴代码了。
OrderController有个创建订单方法,
public class OrderController
{
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public ResultEntity create(Order order)
{
orderService.create(order);
return new ResultEntity(ResultEntity.SUCCESS,"订单创建成功",ResultEntity.NO_DATA);
}
}
对应的Service实现类:
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:下订单->扣库存->减余额->改状态
*/
@Override
public void create(Order order)
{
log.info("----->开始新建订单");
//1 新建订单
orderDao.create(order);
//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了,O(∩_∩)O哈哈~");
}
}
实现类,模拟用户下订单,下订单->扣库存->减余额->改状态。其中订单库,库存库,账户库都不是一个数据库下面,分别有自己的库。此时在一个业务方法里,分别调用订单,库存,账户服务,这样就出现了分布式的事务问题。
此时,在account服务的方法里,模拟一个数字异常。
启动服务,模拟用户下订单请求:
结果订表入了一条数据
库存表更改了库存信息。
账户由于异常,数据并未出现变化。
这样就导致了数据不一致,可能就需要删库跑路了。
此时还原数据。在实现类的方法上加一个注解:
name为自己自定义,rollbackFor:出现异常发生回滚。
account有异常,三个表的数据没有变化,事务回滚。
原理解析
只需在业务方法上加一个@GloableTrancactional注解。
TM开启分布式事务(TM向TC注册全局事务记录),即@GloableTrancactional处。
按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态)
TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务)
TC汇报事务信息,决定分布式事务是提交还是回滚
TC通知所有RM提交/回滚资源,事务二阶段结束
AT模式中:
Seata会拦截业务SQL
一阶段加载:
- 解析SQL语义,找到业务SQL需要更新的业务数据,在业务数据被更新前,将其保存成“before image”
- 执行业务SQL更新业务数据
3. 在执行业务数据更新之后,将其保存成“after image”,最后形成行锁
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段提交:
二阶段顺提交的话,因为业务SQL在一阶段已经提交至数据库,所以Seata框架只需要将一阶段保存的快照数据和行琐删掉,完成数据清理即可。
三阶段回滚:
二阶段如果回滚的话,Seata就需要回滚一阶段已经执行的业务SQL。还原业务数据。回滚方式便是用“before image”还原数据,但在还原数据首先要验证脏写,对比,数据当前数据和after image,一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要人工处理。
总结
- seata社区活跃,短短几个月时间star数已经上W,目前已经更新0.8版本,到1.0版本可供线上环境使用。
- 灵活,对于seata的使用而言,使用非常简单,特别对于AT模式来说,几乎只要加一个注解就能实现分布式事务。
- 高性能,虽然对于使用2pc协议的一个最大诟病就是性能问题,多个库同时锁定造成性能的急剧下降。 而seata在这个基础上有较大的提升,特别对于tcc模式而言。
- 目前TC还不支持集群部署,一旦TC宕机,整个系统分布式事务全都无法处理。
關鍵字: dependency 事务 Seata