分布式事务解决方案(一)Seata集成和使用

  • 前言
  • 准备工作
    • 准备业务场景
    • 搭建Seata的TC Server
      • 添加Nacos配置
      • 建库建表
      • 启动TC 服务
  • 集成Seata
    • 集成到每个参与事务的服务
    • 测试
      • 正常提交
      • 异常回滚
    • Seata 事务模式
      • XA
      • AT (默认)
      • TCC模式和 SAGA模式
      • AT 模式与XA模式的区别

前言

什么是Seata?
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。
官网 https://seata.io/zh-cn/
架构图

准备工作

准备业务场景

准备并启动nacos服务 nacos官网

为了实现分布式事务的场景需要先搭建好一些微服务并注册到nacos

订单服务 - 负责创建订单,减库存,扣余额

    @GetMapping("/createOrder")@GlobalTransactionalpublic String createOrder(){log.info(Thread.currentThread().getName()+":下单");UserOrder entity = new UserOrder();entity.setMoney(100L);entity.setOrderNo(System.currentTimeMillis()+"");orderService.save(entity); //下订单String result = storeService.countDown(1L);//减库存log.info("减库存:"+result);String result1 = userService.spendMoney(1L);//扣钱log.info("扣钱:"+result1);return "ok";}

库存服务 - 扣减库存

@PostMapping("/countDown/{id}")public String countDown(@PathVariable Long id){log.info(Thread.currentThread().getName()+":减库存!");GoodsStore byId = goodsStoreService.getById(id);byId.setCount(byId.getCount()-10);if(byId.getCount()<0){throw new RuntimeException("库存不足");}boolean res = goodsStoreService.updateById(byId);if(res){log.info(Thread.currentThread().getName()+":减库存成功");}else{log.error(Thread.currentThread().getName()+":减库存失败");}return "ok";}

用户服务 - 扣减账户余额

    @GetMapping("/money/spend/{id}")public String spendMoney(@PathVariable Long id){log.info("配置文件:"+name);log.info(Thread.currentThread().getName()+":扣钱");QueryWrapper<SysUserMoney> wrapper = new QueryWrapper<>();wrapper.lambda().eq(SysUserMoney::getUserId,id);SysUserMoney one = userMoneyService.getOne(wrapper);one.setMoney(one.getMoney()-200);if(one.getMoney()<0){throw new RuntimeException("余额不足!");}boolean res = userMoneyService.updateById(one);if(res){log.info(Thread.currentThread().getName()+":更新余额成功");}else{log.error(Thread.currentThread().getName()+":更新余额失败");}return "ok";}

业务场景:订单服务在下订单的时候会分别调用 库存服务扣减库存,用户服务扣减余额。任意一个服务调用失败都将进行事务回滚。

搭建Seata的TC Server

下载:
https://seata.io/zh-cn/blog/download.html
点击下载
下载解压修改配置
1.修改seata/seata-server-1.4.2/conf/registry.conf

# 注册中心
registry {# 注册中心类型 file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos"nacos {application = "seata-tc-server"serverAddr = "192.168.31.46:8848"group = "DEFAULT_GROUP" #nacos中的服务分组, 跟订单,仓促,用户服务同一个组namespace = "3920cea4-c7cc-42e2-a818-3423ec169157" #NACOS 的命名空间IDcluster = "CD" #集群名称username = "nacos"password = "nacos"}
}
# 配置中心 同上
config {# file、nacos 、apollo、zk、consul、etcd3type = "nacos"nacos {serverAddr = "192.168.31.46:8848"namespace = "3920cea4-c7cc-42e2-a818-3423ec169157"group = "DEFAULT_GROUP"username = "nacos"password = "nacos"dataId = "seata-tc-server.properties"}
}

添加Nacos配置

seata-tc-server.properties 内容如下

store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.31.60:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

建库建表

创建MySQL数据库: seata

创建表:

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(`xid`                       VARCHAR(128) NOT NULL,`transaction_id`            BIGINT,`status`                    TINYINT      NOT NULL,`application_id`            VARCHAR(32),`transaction_service_group` VARCHAR(32),`transaction_name`          VARCHAR(128),`timeout`                   INT,`begin_time`                BIGINT,`application_data`          VARCHAR(2000),`gmt_create`                DATETIME,`gmt_modified`              DATETIME,PRIMARY KEY (`xid`),KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(`branch_id`         BIGINT       NOT NULL,`xid`               VARCHAR(128) NOT NULL,`transaction_id`    BIGINT,`resource_group_id` VARCHAR(32),`resource_id`       VARCHAR(256),`branch_type`       VARCHAR(8),`status`            TINYINT,`client_id`         VARCHAR(64),`application_data`  VARCHAR(2000),`gmt_create`        DATETIME(6),`gmt_modified`      DATETIME(6),PRIMARY KEY (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(`row_key`        VARCHAR(128) NOT NULL,`xid`            VARCHAR(128),`transaction_id` BIGINT,`branch_id`      BIGINT       NOT NULL,`resource_id`    VARCHAR(256),`table_name`     VARCHAR(32),`pk`             VARCHAR(36),`status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',`gmt_create`     DATETIME,`gmt_modified`   DATETIME,PRIMARY KEY (`row_key`),KEY `idx_status` (`status`),KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;CREATE TABLE IF NOT EXISTS `distributed_lock`
(`lock_key`       CHAR(20) NOT NULL,`lock_value`     VARCHAR(20) NOT NULL,`expire`         BIGINT,primary key (`lock_key`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

启动TC 服务

[root@localhost bin]# pwd
/usr/local/seata/seata-server-1.4.2/bin
[root@localhost bin]# ll
总用量 12
-rwxr-xr-x. 1 502 games 3685 4月  25 2021 seata-server.bat
-rwxr-xr-x. 1 502 games 4212 4月  25 2021 seata-server.sh
[root@localhost bin]# nohup ./seata-server.sh >log.out 2>1 &


到这里 Seata-tc-server 启动完成了

集成Seata

集成到每个参与事务的服务

引入依赖:

        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><!-- 排除 1.3.0 的依赖 改为1.4.2的依赖包 --><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><version>1.4.2</version></dependency>

修改配置:
application.yaml
建议复制粘贴,手写容易打错字。

# seata 分布式事务配置
seata:application-id: ${spring.application.name}tx-service-group: my-group # 事务组,根据这个获取集群名称registry:type: nacosnacos:server-addr: ${spring.cloud.nacos.server-addr}group: DEFAULT_GROUPnamespace: "3920cea4-c7cc-42e2-a818-3423ec169157"username: nacospassword: nacosapplication: seata-tc-serverservice:vgroup-mapping: # 事务组与TC服务 集群的映射关系my-group: CDdata-source-proxy-mode: XA  # 默认是AT模式

启动我们的三个服务

2022-01-06 18:58:52.159  INFO 2552 --- [           main] i.s.c.r.netty.NettyClientChannelManager  : will connect to 192.168.31.56:8091
2022-01-06 18:58:52.162  INFO 2552 --- [           main] i.s.c.rpc.netty.RmNettyRemotingClient    : RM will register :jdbc:mysql://192.168.31.60:3306/storecenter
2022-01-06 18:58:52.176  INFO 2552 --- [           main] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:RMROLE,address:192.168.31.56:8091,msg:< RegisterRMRequest{resourceIds='jdbc:mysql://192.168.31.60:3306/storecenter', applicationId='store-service', transactionServiceGroup='my-group'} >
2022-01-06 18:58:53.181  INFO 2552 --- [           main] i.s.c.rpc.netty.RmNettyRemotingClient    : register RM success. client version:1.4.2, server version:1.4.2,channel:[id: 0x8b1b6db5, L:/192.168.31.53:61915 - R:/192.168.31.56:8091]
2022-01-06 18:58:53.201  INFO 2552 --- [           main] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 171 ms, version:1.4.2,role:RMROLE,channel:[id: 0x8b1b6db5, L:/192.168.31.53:61915 - R:/192.168.31.56:8091]
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@362b384c

通过日志是能够看到RM 成功注册到了 seata-tc-server 服务器上了。

测试

通过 @GlobalTransactional 注解配置事务的入口

正常提交

先测试正常的情况, 余额充足。
初始数据:

浏览器发起请求

再次查看数据

整个过程是处于正常的情况
创建了订单, 减了库存10, 扣了余额200
后台日志

2022-01-06 20:58:36.396  INFO 14052 --- [nio-8080-exec-2] i.seata.tm.api.DefaultGlobalTransaction  : Begin new global transaction [192.168.31.56:8091:2459182619839676463]
2022-01-06 20:58:36.397  INFO 14052 --- [nio-8080-exec-2] c.r.d.o.controller.IndexController       : http-nio-8080-exec-2:下单
2022-01-06 20:58:37.036  INFO 14052 --- [nio-8080-exec-2] c.r.d.o.controller.IndexController       : 减库存:ok
2022-01-06 20:58:37.403  INFO 14052 --- [nio-8080-exec-2] c.r.d.o.controller.IndexController       : 扣钱:ok
2022-01-06 20:58:37.513  INFO 14052 --- [ch_RMROLE_1_3_8] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=192.168.31.56:8091:2459182619839676463,branchId=2459182619839676465,branchType=XA,resourceId=jdbc:mysql://192.168.31.60:3306/ordercenter,applicationData=null
2022-01-06 20:58:37.514  INFO 14052 --- [ch_RMROLE_1_3_8] io.seata.rm.AbstractRMHandler            : Branch committing: 192.168.31.56:8091:2459182619839676463 2459182619839676465 jdbc:mysql://192.168.31.60:3306/ordercenter null
2022-01-06 20:58:37.637  INFO 14052 --- [ch_RMROLE_1_3_8] i.s.rm.datasource.xa.ResourceManagerXA   : 192.168.31.56:8091:2459182619839676463-2459182619839676465 was committed.
2022-01-06 20:58:37.638  INFO 14052 --- [ch_RMROLE_1_3_8] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed
2022-01-06 20:58:38.672  INFO 14052 --- [nio-8080-exec-2] i.seata.tm.api.DefaultGlobalTransaction  : Suspending current transaction, xid = 192.168.31.56:8091:2459182619839676463
2022-01-06 20:58:38.674  INFO 14052 --- [nio-8080-exec-2] i.seata.tm.api.DefaultGlobalTransaction  : [192.168.31.56:8091:2459182619839676463] commit status: Committed
异常回滚

当在次发起请求由于余额低于200 会出现扣减余额失败的情况


查看数据库的数据

订单未产生,库存未变更, 余额也未扣减。 说明事务已经回滚了。
查看日志

Seata 事务模式

XA

优点:
事务强一致性,满足ACID原则
常用数据库都支持,实现简单,没有代码侵入
缺点:
因为一阶段需要锁定数据库资源,等待第二阶段结束才释放,性能较差 牺牲了可用性, 保证一致性
依赖关系型数据库实现事务。

AT (默认)

优点
1阶段直接提交,记录undo_log 日志 01 事务提交之前的日志
2阶段提交,先记录日志02 进行对比,如果和提交之前的一致那么就删日志, 事务完成。
如果二阶段提交时发现和之前的日志不一致(不一致的原因,其他事务非Seata 管理的事务对数据进行了操作),则抛异常, 需要人工介入
2阶段回滚,先记录日志02 和日志01进行对比,如果和提交之前的一致那么就使用日志进行回滚在删除日志, 事务完成。
如果二阶段回滚时发现和之前的日志不一致(不一致的原因,其他事务非Seata 管理的事务对数据进行了操作),则抛异常, 需要人工介入
不需要等待
引入了全局锁 多个事务并发提交的时候需要获取表数据对应的全局锁,针对死锁的情况,如果超过时间未获取到全局锁则释放当前持有的锁,并回滚数据。
没有代码上的侵入,每个参与事务的服务需要单独建表。
缺点:
两阶段之间属于软状态,属于最终一致性。
框架的快照会影响数据库性能,但比XA模式要好很多

默认是AT 模式, 需要在参与事物的服务数据库创建表

CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',`xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',`context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',`log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',`log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',`log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
TCC模式和 SAGA模式

这两种模式需要人工手写业务逻辑代码对事务回滚事件做补偿的业务逻辑。 这里就没有做实现了。 感兴趣的小伙伴百度一下。

AT 模式与XA模式的区别

XA 模式一阶段不提交事务 锁定资源 ,AT 模式一阶段直接提交不锁定资源
XA 模式依赖数据库回滚机制实现回滚,AT 模式利用数据快照实现数据回滚
XA 强一致,AT 最终一致

分布式事务解决方案(一)Seata集成和使用相关推荐

  1. 分布式事务解决方案,Seata的基本配置和使用

    文章目录 1. 分布式事务介绍 ①:本地事务 ②:分布式事务 ③:常见的分布式事务解决方案 3. 2PC与3PC ①:2PC与3PC的区别 3. Seata介绍 ①:Seata的三种角色 ②:Seat ...

  2. 分布式事务解决方案:seata

    Seata控制分布式事务 1).每一个微服务必须创建undo_Log SEATA AT 模式需要 UNDO_LOG 表 -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log CREATE ...

  3. 分布式事务解决方案之2PC(两阶段提交)入门简介

    什么是2PC?       2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase).提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提 ...

  4. 分布式事务专题(三):分布式事务解决方案之2PC(两阶段提交)

    目录: 基础概念 分布式事务理论 分布式事务解决方案之2pc(本章) 分布式事务解决方案之TCC 分布式事务解决方案之可靠消息最终一致性 分布式事务解决方案之最大努力通知 分布式事务综合案例分析 3. ...

  5. 高并发系统设计——分布式事务解决方案

    摘要​​​​​​​ 有一定分布式开发经验的朋友都知道,产品项目/系统最初为了能够快速迭代上线,往往不太注重产品项目系统的高可靠性.高性能与高扩展性,采用单体应用和单实例擞据痒的架构方式快速迭代开发;当 ...

  6. Spring Boot之基于Dubbo和Seata的分布式事务解决方案

    转载自 Spring Boot之基于Dubbo和Seata的分布式事务解决方案 1. 分布式事务初探 一般来说,目前市面上的数据库都支持本地事务,也就是在你的应用程序中,在一个数据库连接下的操作,可以 ...

  7. Seata聚合 AT、TCC、SAGA 、 XA事务模式打造一站式的分布式事务解决方案

    Seata Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务.在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一 ...

  8. seata 集群_【视频】 聊聊分布式事务解决方案seata

    什么seata Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务.在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布 ...

  9. 分布式事务解决方案Seata

    一.Seata 简介 Seata 是 阿里巴巴2019年开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务.在 Seata 开源之前,Seata 对应的内部版本在阿里内 ...

  10. 分布式事务解决方案 - SpringCloud Alibaba Seata

    目录 github代码:GitHub - 18409489244/seata: 基于springcloud alibaba seata 的分布式事务demo 一.常见分布式事务解决方案 二.分布式事务 ...

最新文章

  1. Docker容器入门-基本命令的使用
  2. AI医疗领域人才需求与培养趋势分析
  3. Python种reverse和reversed反转列表的操作方法
  4. JAVA通信编程(三)——TCP通讯
  5. Asp.net Core Jenkins Docker 实现一键化部署
  6. 优秀的 jQuery 文本输入框自动完成 自动提示插件
  7. TIN与DEM的区别和联系
  8. Freemarker中通过request获得contextPath
  9. 【java】深入探讨 Java 类加载器
  10. [emuch.net]MatrixComputations(7-12)
  11. unity 插件curvy做出来的效果以及 curvy的部分BUG(也算不上BUG吧)
  12. 判断文件是否为图片格式
  13. 小程序图片实现自适应大小,超过部分自动裁剪
  14. UWCN开源的Pay企业级开源聚合支付系统
  15. 什么叫股指期货,股指期货交易流程是什么
  16. Enterprise Architect使用教程
  17. 基于OpenCASCADE自制三维建模软件(三)搭建开发环境
  18. vue下利用canvas实现在线图片标注
  19. The 2022 ICPC Asia Regionals Online Contest (II) J
  20. 这可能是目前世界上最全的流媒体知识科普文章

热门文章

  1. Modem Router
  2. 第十五周博客作业西北师范大学|李晓婷
  3. yolov5测试单张图片
  4. cdrom是什么意思_开启电脑时出现CDROM是什么意思
  5. win7系统rpc服务器不可用无法开机,win7系统RPC服务器不可用的解决方法
  6. ubuntu18.04前置面板声音输出方法
  7. Compiling for iOS 10.0, but module ‘xxx‘ has a minimum deployment target of iOS 12.0
  8. golang反射的类型Type与种类Kind使用
  9. linux的核心安装下的命令行,linux lilo命令参数及用法详解--linux安装核心载入,开机管理程...
  10. 编译出错 Resource temporarily unavailable