Seata AT模式开发实战

Seata AT模式开发实战

一、样例简介

Seata官网提供了很多样例工程来学习AT模式。我们选择springboot-dubbo-seata项目来学习,这是一个Spring Boot + Dubbo +Seata的示例。

样例的代码结构如图所示:

其中 samples-account是账户模块,samples-business是业务模块,samples-common是公共模块,samples-order是订单模块,samples-stock是库存模块,sql文件夹存储SQL语句。

账户模块、订单模块和库存模块是以Dubbo服务的形式提供服务的,业务模块是全局事务发起方(即事务注解@GlobalTransactional的声明者)

,它定义了全局事务的范围。

二、准备工作

1、数据库

创建mysql数据库,库名为seata;然后执行初始化脚本db_seata.sql。脚本创建了四个表:t_account、t_order、t_stock、undo_log。其中undo_log是Seata框架AT模式要用到的事务日志表,其余3张表都是业务表。

(为了简单,实例将业务表都放在同一个数据库中,实际上也可以将它们放在不同的数据库里。如果三张表分数3个不同的数据库,则这3个数据库都要创建undo_log这张表。)

2、注册中心

实例采用的注册中心是Nacos,为了防止因为dubbo,nacos因版本不匹配出现的心跳请求出错的情况,使用Nacos1.1.0版本。下载地址为https://github.com/alibaba/nacos/releases/tag/1.1.0

下载解压后,在bin目录下执行startup.cmd脚本即可启动Nacos,端口为8848,

image-20220102165840276

通过浏览器http://127.0.0.1:8848/nacos访问控制台,默认用户名和密码都是nacos。

3、Seata Server

从Seata官网下载Seata Server,下载并解压缩后进行斌目录下执行以下命令

seata-server.bat -p 8091 -h 127.0.0.1 -m file

运行效果如下图所示:

image-20220102165748106

Seata Server就是事务协调器,为了简单,此处使用的是单机版本,非高可用版本。

三、运行样例工程

依次启动四个工程, samples-account、samples-order、samples-stock、samples-business,启动前修改配置文件application.properties中的数据库信息,启动完4个工程后,通过Nacos控制台(http://127.0.0.1:8848/nacos/#/serviceManagement)可以看到服务已准备就绪,

image-20220102163124153

从图中可以看到,启动了3个Dubbo服务提供者和3个服务消费者,正常运行。

在运行事务前,先检查下数据库中数据:

  • t_account中只有一行记录,用户ID(user_id)为1,账户余额(amount)为4000
  • t_stock中只有一行记录,商品编码(commodity_code)为“C201901140001”,名字(name)为“水杯”,数量(count)为1000个
  • t_order没有记录

四、验证AT模式分布式事务

1、测试全局事务提交

1、使用Postman工具发送一个POST请求“http://localhost:8104/business/dubbo/buy”。消息体为:

{
    "userId": "1",
    "commodityCode":"C201901140001",
    "name": "水杯",
    "count": 3,
    "amount": 150
}

表示购买3个水杯,花费150元。发出请求后,得到返回值200,表示请求处理成功。

再次检查数据库的值:

  • t_account中只有一行记录,用户ID(user_id)为1,账户余额(amount)为3850
  • t_stock中只有一行记录,商品编码(commodity_code)为“C201901140001”,名字(name)为“水杯”,数量(count)变为997个
  • t_order中增加了一条记录,表示发生了一笔交易,购买了三个商品编号为C201901140001的商品(即三个水杯),交易额为150元。

image-20220102170554354

看一下business工程日志,开了一个全局事务8367903791028170753,并完成了全局事务提交:

2022-01-02 12:33:00.590  INFO 18712 --- [nio-8104-exec-3] i.s.s.i.c.controller.BusinessController  : 请求参数:BusinessDTO(userId=1, commodityCode=C201901140001, name=水杯, count=3, amount=150)
2022-01-02 12:33:00.599  INFO 18712 --- [nio-8104-exec-3] io.seata.tm.TransactionManagerHolder     : TransactionManager Singleton io.seata.tm.DefaultTransactionManager@5d187a3a
2022-01-02 12:33:00.656  INFO 18712 --- [nio-8104-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [172.26.112.1:8091:8367903791028170753]
开始全局事务,XID = 172.26.112.1:8091:8367903791028170753
2022-01-02 12:33:01.471  INFO 18712 --- [nio-8104-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = 172.26.112.1:8091:8367903791028170753
2022-01-02 12:33:01.472  INFO 18712 --- [nio-8104-exec-3] i.seata.tm.api.DefaultGlobalTransaction  : [172.26.112.1:8091:8367903791028170753] commit status: Committed

接着看一下storage工程日志。它参与了这个全局事务,创建了一个分支事务8367903791028170754,并完成了二阶段分支事务提交:

全局事务id :172.26.112.1:8091:8367903791028170753
2022-01-02 12:33:01.867  INFO 16728 --- [h_RMROLE_1_1_24] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=172.26.112.1:8091:8367903791028170753,branchId=8367903791028170754,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata,applicationData=null
2022-01-02 12:33:01.868  INFO 16728 --- [h_RMROLE_1_1_24] io.seata.rm.AbstractRMHandler            : Branch committing: 172.26.112.1:8091:8367903791028170753 8367903791028170754 jdbc:mysql://127.0.0.1:3306/seata null
2022-01-02 12:33:01.869  INFO 16728 --- [h_RMROLE_1_1_24] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

接着看一下account工程日志。它参与了这个全局事务,创建了一个分支事务8367903791028170755,并完成了二阶段分支事务提交:

全局事务id :172.26.112.1:8091:8367903791028170753
2022-01-02 12:33:01.162  WARN 17452 --- [:20880-thread-2] ServiceLoader$InnerEnhancedServiceLoader : Load [io.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail. io/protostuff/runtime/IdStrategy
2022-01-02 12:33:01.875  INFO 17452 --- [h_RMROLE_1_1_24] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=172.26.112.1:8091:8367903791028170753,branchId=8367903791028170755,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata,applicationData=null
2022-01-02 12:33:01.876  INFO 17452 --- [h_RMROLE_1_1_24] io.seata.rm.AbstractRMHandler            : Branch committing: 172.26.112.1:8091:8367903791028170753 8367903791028170755 jdbc:mysql://127.0.0.1:3306/seata null
2022-01-02 12:33:01.877  INFO 17452 --- [h_RMROLE_1_1_24] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

最后看一下order工程日志。。它参与了这个全局事务,创建了一个分支事务8367903791028170756,并完成了二阶段分支事务提交:

全局事务id :172.26.112.1:8091:8367903791028170753
2022-01-02 12:33:01.881  INFO 2832 --- [h_RMROLE_1_1_24] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=172.26.112.1:8091:8367903791028170753,branchId=8367903791028170756,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata,applicationData=null
2022-01-02 12:33:01.883  INFO 2832 --- [h_RMROLE_1_1_24] io.seata.rm.AbstractRMHandler            : Branch committing: 172.26.112.1:8091:8367903791028170753 8367903791028170756 jdbc:mysql://127.0.0.1:3306/seata null
2022-01-02 12:33:01.883  INFO 2832 --- [h_RMROLE_1_1_24] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed
2、测试全局事务回滚
//打开注释测试事务发生异常后,全局回滚功能
//        if (!flag) {
//            throw new RuntimeException("测试抛异常后,分布式事务回滚!");
//        }

重启samples-business工程,重新发送POST请求,可以看到返回操作失败,如下图所示:

![image-20220102194209064](I:\Program\Node\Seata\Seata AT模式开发实战.assets\image-20220102194209064.png)

再检查数据库,发现3张表里的数据与上次正常提交测试后的数据完全一样,没有发生数据改变。

接着来看一下BusinessServiceImpl.handleBusiness()方法的具体实现:

    @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 stockResponse = stockDubboService.decreaseStock(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 (stockResponse.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;
    }

从代码中可以看到,声明了开启全局事务注解@GlobalTransactional,表示在该方法中调用的服务都在一个分布式服务中。可以看到,在该方法中,调用扣减库存服务更新t_stock表中的数据,并调用了创建订单服务,由于flag的变量值为false,所以,后面抛出异常。

接下来看一下创建订单服务的代码:

    public ObjectResponse<OrderDTO> createOrder(OrderDTO orderDTO) {
        ObjectResponse<OrderDTO> response = new ObjectResponse<>();
        //扣减用户账户
        AccountDTO accountDTO = new AccountDTO();
        accountDTO.setUserId(orderDTO.getUserId());
        accountDTO.setAmount(orderDTO.getOrderAmount());
        ObjectResponse objectResponse = accountDubboService.decreaseAccount(accountDTO);

        //生成订单号
        orderDTO.setOrderNo(UUID.randomUUID().toString().replace("-", ""));
        //生成订单
        TOrder tOrder = new TOrder();
        BeanUtils.copyProperties(orderDTO, tOrder);
        tOrder.setCount(orderDTO.getOrderCount());
        tOrder.setAmount(orderDTO.getOrderAmount().doubleValue());
        try {
            baseMapper.createOrder(tOrder);
        } catch (Exception e) {
            response.setStatus(RspStatusEnum.FAIL.getCode());
            response.setMessage(RspStatusEnum.FAIL.getMessage());
            return response;
        }

        if (objectResponse.getStatus() != 200) {
            response.setStatus(RspStatusEnum.FAIL.getCode());
            response.setMessage(RspStatusEnum.FAIL.getMessage());
            return response;
        }

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

可以看到,创建订单服务除了在t_order表中插入一条记录外,还会调用accountDubboService.decreaseAccount()方法扣减用户账户余额服务,即更新t_account表中的记录。

如果没有声明Seata全局事务,则在后面抛出异常时,前面调用的3个服务已经完成,那3张彪的数据应该已经变更了,但实际情况是,这3张表的数据没有变更,整个全局事务正确完成了回滚。

此外可以看一下几个工程中的日志。

先看一下business工程日志:

开始全局事务,XID = 172.26.112.1:8091:8367903791028170757
2022-01-02 19:41:15.532  INFO 18844 --- [nio-8104-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = 172.26.112.1:8091:8367903791028170757
2022-01-02 19:41:15.533  INFO 18844 --- [nio-8104-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : [172.26.112.1:8091:8367903791028170757] rollback status: Rollbacked
2022-01-02 19:41:15.545 ERROR 18844 --- [nio-8104-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 测试抛异常后,分布式事务回滚!] with root cause

再看一下stock工程日志,

全局事务id :172.26.112.1:8091:8367903791028170757
2022-01-02 19:41:15.456  INFO 16728 --- [h_RMROLE_1_2_24] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=172.26.112.1:8091:8367903791028170757,branchId=8367903791028170758,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata,applicationData=null
2022-01-02 19:41:15.457  INFO 16728 --- [h_RMROLE_1_2_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 172.26.112.1:8091:8367903791028170757 8367903791028170758 jdbc:mysql://127.0.0.1:3306/seata
2022-01-02 19:41:15.527  INFO 16728 --- [h_RMROLE_1_2_24] i.s.r.d.undo.AbstractUndoLogManager      : xid 172.26.112.1:8091:8367903791028170757 branch 8367903791028170758, undo_log deleted with GlobalFinished
2022-01-02 19:41:15.527  INFO 16728 --- [h_RMROLE_1_2_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

然后看一下account工程日志,

全局事务id :172.26.112.1:8091:8367903791028170757
2022-01-02 19:41:15.374  INFO 17452 --- [h_RMROLE_1_2_24] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=172.26.112.1:8091:8367903791028170757,branchId=8367903791028170759,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata,applicationData=null
2022-01-02 19:41:15.376  INFO 17452 --- [h_RMROLE_1_2_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 172.26.112.1:8091:8367903791028170757 8367903791028170759 jdbc:mysql://127.0.0.1:3306/seata
2022-01-02 19:41:15.451  INFO 17452 --- [h_RMROLE_1_2_24] i.s.r.d.undo.AbstractUndoLogManager      : xid 172.26.112.1:8091:8367903791028170757 branch 8367903791028170759, undo_log deleted with GlobalFinished
2022-01-02 19:41:15.451  INFO 17452 --- [h_RMROLE_1_2_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

最后看一下order工程日志,

全局事务id :172.26.112.1:8091:8367903791028170757
2022-01-02 19:41:15.290  INFO 2832 --- [h_RMROLE_1_2_24] i.s.c.r.p.c.RmBranchRollbackProcessor    : rm handle branch rollback process:xid=172.26.112.1:8091:8367903791028170757,branchId=8367903791028170760,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata,applicationData=null
2022-01-02 19:41:15.291  INFO 2832 --- [h_RMROLE_1_2_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 172.26.112.1:8091:8367903791028170757 8367903791028170760 jdbc:mysql://127.0.0.1:3306/seata
2022-01-02 19:41:15.369  INFO 2832 --- [h_RMROLE_1_2_24] i.s.r.d.undo.AbstractUndoLogManager      : xid 172.26.112.1:8091:8367903791028170757 branch 8367903791028170760, undo_log deleted with GlobalFinished
2022-01-02 19:41:15.370  INFO 2832 --- [h_RMROLE_1_2_24] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked

通过上述日志,可以看到它们参与了这个全局事务,创建了分支事务,并完成了二阶段分支事务回滚。4个工程的日志很清晰的说明了分布式事务的开始及二阶段回滚过程,通过日志,我们可以理解Seata事务的工作流程。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇