Seata 简介

引言

Seata 的前身 Fescar 刚开源的时候,就看过相关的文章和代码,代码写得很好,我还在另一个自己的项目中,借鉴了它的很多设计风格。最近想总结一篇关于分布式事务的文章,所以就想以 Seata 为中心,围绕它来细述分布式事务的点点滴滴。本文作为该系列文章的开篇,先简单地介绍一下 Seata 的背景和使用方式,其他 Seata 相关文章均收录于 <Seata系列文章>中。

背景

互联网系统最初的设计一般都是单库单表,但随着业务数据规模的快速发展,数据量越来越大,单库单表逐渐成为瓶颈。所以,在这个阶段一般都会对数据库进行水平拆分,将原单库单表拆分成多个数据库分片。

如下图所示,分库分表之后,原来在一个数据库上就能完成的写操作,可能就会跨多个数据库,这就产生了跨数据库事务问题。
hor-db
此外,在系统设计初期,一般都是将所有业务放在一个服务中,但是随着业务的快速发展,系统的访问量和业务复杂程度都在快速增长,这种单系统架构逐渐成为业务发展瓶颈,解决业务系统的高耦合、可伸缩问题的需求越来越强烈。
vir-db
如上图所示,本着面向服务(SOA)的设计原则,会将单系统拆分成多个业务系统,这降低了各个系统之间的耦合度,使不同的业务系统专注于自身业务,更有利于各个子业务的发展和子系统容量的伸缩。但是,业务系统按照服务拆分之后,一个完整的业务往往需要调用多个服务,如何保证多个服务间的数据一致性成为一个难题。

为了应对这种需求,在数据库领域发展出了一个 XA 协议,在数据库层面基于 2PC 来实现分布式事务,但是并不是所有的数据库都支持该协议,如果有任意一个子系统使用的数据库不支持 XA 协议的话,就无法保证整个业务流程的一致性。此外,XA 的执行效率也很差,而且因为它处于数据库实现领域,所以数据库的使用者对此毫无办法,当然您也可以直接定制化数据库实现,但是成本很大。

为了解决上述问题,专注于分布式事务的应用层中间件就应运而生,它们有的是 2PC 型无侵入方案,有的是 TCC 方案。而我们今天介绍的主角 Seata,将各种方案都整合到了一起,并针对性能进行了很多优化,可谓是集大成者。接下来就让我们一起看一看 Seata 带给我们的便利,以及 Seata 的实现原理。

试玩

前面提到了 Seata 会给开发带来极大的便利,这里就以官方Demo为例,先给大家演示一下如何使用 Seata。这里我使用了官方 Demo 中的 springboot-dubbo-seata,因为它涉及到前面提到的多服务场景,每个服务操作各自的 DB(名义上),使用 dubbo 进行 RPC,从而完成整个业务流程。

我的试玩流程:

  1. 下载代码
  2. 导入 IntelliJ IDEA,并下载 maven 依赖
  3. 下载 1.1.0 版本的 nacos,后面会用它进行 RPC 的服务发现
  4. 下载 0.8.0 版本的 seata-server,这里它扮演分布式事务协调者
  5. 在本地的 MySQL 服务中创建 seata 数据库,并将 springboot-dubbo-seata 中的样例 SQL 导入到 seata 数据库,完成后可以发现数据库中有 t_account,t_order,t_storage,undo_log 这四个表,并且 t_account 中有一条用户数据,t_storage 中有一条商品信息。
  6. 然后分别启动 3 个子服务 samples-accountsamples-ordersamples-storage,最后启动业务入口服务 samples-business
  7. 通过 curl 调用业务入口服务

⚠️注意:我当时测试时,官方 Guide 中使用的 curl 命令有一些问题,其中使用的商品码有误,这里需要将其改为 DB 中保存的商品码 C201901140001,我已经给官方仓库提了 PR,但是可能还未合并。总之,最终完整的 curl 命令如下:

1
curl -H "Content-Type:application/json" -X POST -d '{"userId":"1","commodityCode":"C201901140001","name":"风扇","count":2,"amount":"100"}' localhost:8104/business/dubbo/buy

事务提交

sample-success.png
当业务流程成功完成时,请求结果是成功。查看 DB 你会发现 t_account 中用户的余额减少了:4000 -> 3900,t_storage 中商品的数量减少了: 1000 -> 998,t_order 中新增了一条订单。
new-order

事务回滚

接下来我们测试一下事务回滚的效果,首先打开回滚异常。
rollback-check
然后重启入口服务 samples-business,并重新发送请求,请求结果变成了失败,此时查看 DB 你会发现它没有任何变化。
sample-failure
我们回过头来看看业务入口的代码,你会发现这一切特性的接入对开发者来说,只是将全局事务注解 GlobalTransactional 标在入口函数上,然后在所有服务中引入 Seata,并进行正确的配置,剩下的脏活累活 Seata 就自动帮我们完成了, 是不是感觉很神奇?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 处理业务逻辑
*/
@Override
// 全局事务注解
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-gts-seata-example")
public ObjectResponse handleBusiness(BusinessDTO businessDTO) {
System.out.println("开始全局事务,XID = " + RootContext.getXID());
ObjectResponse<Object> objectResponse = new ObjectResponse<>();
//1、扣减库存
CommodityDTO commodityDTO = new CommodityDTO();
commodityDTO.setCommodityCode(businessDTO.getCommodityCode());
commodityDTO.setCount(businessDTO.getCount());
ObjectResponse storageResponse = storageDubboService.decreaseStorage(commodityDTO);
//2、创建订单
OrderDTO orderDTO = new OrderDTO();
orderDTO.setUserId(businessDTO.getUserId());
orderDTO.setCommodityCode(businessDTO.getCommodityCode());
orderDTO.setOrderCount(businessDTO.getCount());
orderDTO.setOrderAmount(businessDTO.getAmount());
ObjectResponse<OrderDTO> response = orderDubboService.createOrder(orderDTO);

//打开注释测试事务发生异常后,全局回滚功能
if (!flag) {
throw new RuntimeException("测试抛异常后,分布式事务回滚!");
}

if (storageResponse.getStatus() != 200 || response.getStatus() != 200) {
throw new DefaultException(RspStatusEnum.FAIL);
}

objectResponse.setStatus(RspStatusEnum.SUCCESS.getCode());
objectResponse.setMessage(RspStatusEnum.SUCCESS.getMessage());
objectResponse.setData(response.getData());
return objectResponse;
}

从业务服务的实现代码中可以看到,我们先调用了库存服务减少库存,然后调用订单服务创建订单,在订单服务里面,涉及到了减少用户余额(代码就不展示了),当所有的这一切都完成时,业务流程中抛出一个运行时异常,致使整个业务链路中的所有 DB 事务都回滚了。

至此,系统垂直扩展引入的分布式事务问题就解决了。
vir-db2
好了,实验课结束了,该学一下理论知识了。

参考内容

[1] fescar锁设计和隔离级别的理解
[2] 分布式事务中间件 Fescar - RM 模块源码解读
[3] Fescar分布式事务实现原理解析探秘
[4] Seata TCC 分布式事务源码分析
[5] 深度剖析一站式分布式事务方案 Seata-Server
[6] 分布式事务 Seata Saga 模式首秀以及三种模式详解
[7] 蚂蚁金服大规模分布式事务实践和开源详解
[8] 分布式事务 Seata TCC 模式深度解析
[9] Fescar (Seata)0.4.0 中文文档教程
[10] Seata Github Wiki
[11] 深度剖析一站式分布式事务方案Seata(Fescar)-Server

贝克街的流浪猫 wechat
您的打赏将鼓励我继续分享!
  • 本文作者: 贝克街的流浪猫
  • 本文链接: https://www.beikejiedeliulangmao.top/middleware/seata/introduction/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 创作声明: 本文基于上述所有参考内容进行创作,其中可能涉及复制、修改或者转换,图片均来自网络,如有侵权请联系我,我会第一时间进行删除。