SpringCloud 整合分布式事务组件 Seata (八)

前言

近期一直在忙项目,我也是打工仔。不多说,我们开始玩一玩seata。


 

正文

什么都不说,我们按照惯例,先上一个图(图里不规范的使用请忽略):

简单一眼就看出来, 比我们平时用的东西,多了  Seata Server 微服务

同样这个 Seata Server 微服务  ,也是需要注册到eureka上面去的。 

那么我们首先就搞一搞这个 seata server ,那么剩下的就是一些原本的业务服务整合配置了。

该篇用的 seata server 版本,用的是1.4.1 , 可以去git下载下。
当然,我也是给你们备了的:

seata server 1.4.1
某度网盘分享地址:

https://pan.baidu.com/s/1R9McfkSkoj72Pf98ugCvBw

提取码:

4ynp

第一步,下载下来解压 :

第二步,创个 seata server 用的数据库 :
 

CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `branch_id` bigint(20) NOT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`),
  KEY `idx_branch_id` (`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建完后:

第三步,修改下 \seata-1.4.1\seata-server-1.4.1\seata\conf 里的配置文件一些信息 :

1. registry.conf

ok,registry.conf 这文件就修改这些配置项。

2. file.conf :

以上两个文件配置完(记得保存), 我们先把我们的注册中心 eureka服务跑起来,然后点击启动 seata server:

可以看到启动成功(前提是eureka已经启动):

第三步 ,配置我们需要用到 分布式事务 seata组件的 微服务 :

我这里的示例实践,需要用到的有2个微服务 :

那么我们这两个微服务都需要做点什么呢?

1. 在对应的微服务的对应的不同数据库里(只要你想用上seata的), 都加上undo_log 这个表:

SQL语句:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime(0) NULL,
  `log_modified` datetime(0) NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

2. 关键步骤了 , 导入jar包 (需要用到seata组件都服务都需要导入)

		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-alibaba-seata</artifactId>
			<version>2.1.0.RELEASE</version>
			<exclusions>
				<exclusion>
					<artifactId>seata-all</artifactId>
					<groupId>io.seata</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<artifactId>seata-all</artifactId>
			<groupId>io.seata</groupId>
			<version>1.4.1</version>
		</dependency>

3.更加关键了,就是做配置

先总得了解下,需要用到seata的 微服务需要做些什么配置 ?

1. 在resources 下 新增2个配置文件 , file.conf  和   registry.conf

2.yml 新增配置seata 事务组参数

3.代码调整数据源代理,交给seata代理

1.   registry.conf  

以上是业务微服务里的registry.conf  需要改动的配置信息 ,给出一份该篇文章使用的:
 

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka/"
    application = "seata-server"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = 0
    password = ""
    cluster = "default"
    timeout = 0
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
    apolloAccesskeySecret = ""
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

file.conf

给出一份该篇使用的完整的:
 

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
#这里注意,等号前后都是配置,前面是yml里配置的事务组,后面是register.conf里定义的seata-server
  vgroupMapping.test_tx_group = "seata-server"
  #only support when registry.type=file, please don't set multiple addresses
  seata_tc_server.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}
client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

2. 需要在yml配置文件加上配置项,指明当前服务使用了 seata分布式事务组件,且需要加入的分布式事务组是哪个: 

spring:
  cloud:
    alibaba:
      seata.tx-service-group: test_tx_group

3.然后是需要将数据源交给seata去代理:
 

去掉默认自动加载数据源

配置dao层扫描位置

@MapperScan("com.cloud.client1.dao")
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

然后是seata代理数据源(红色标注的地方需要注意下,都是实打实踩出来的坑。导入类的来源以及扫描的mapper的xml位置):

ok,到这里一个微服务 usercent的 整合 seata 分布式组件算是完成了。 

同样,我们继续对 另外一个微服务 coredata 做seata 分布式组件  整合 。

也是第一步,file.conf 和 registry.conf  (其实我们现在就是让这两个微服务在同一个分布式事务组,我们直接把刚才在usercent那边的两个文件直接粘贴过来就好)。

然后第二步就是修改yml配置:

第三步就是新增seata数据源代理、application上的注解排除自动加载数据源和扫描dao层地址,yml上面的配置项:

完事,两个业务微服务都整合了 分布式事务组件 seata ,而且都设置在同个分布式事务组里 (test_tx_group).

那么到这其实已经完事了, 接下来玩下 分布式事务回滚场景 的例子 (怎么去使用)。

PS: 使用方式关注 @GlobalTransactional  注解, 想把全局事务从哪里开启,就把这个注解放到哪个方法上去, 这个方法内 调用的其他服务只要都使用了seata,且在同个事务组,那么就会加入到当前的全局事务里。 

我该篇就不弄太多服务了,就弄了2个分布式服务。   
 

展示的例子内容 :


1. 上游服务 出错, 触发分布式事务, 上下游服务都会事务回滚;

2.下游服务 出错,触发分布式事务, 上下游服务都会事务回滚;(某种程度上讲,下游出错如果只有一个下游,其实不需分布式事务,通过错误传递单个回滚也是可以的。但是如果服务调用链很长,中游服务出错,需要整个链上的服务都事务回滚,那么就有必要都使用seata )

我们开始模拟: 

第一个场景

上游服务 coredata 使用seata全局事务注解@GlobalTransactional 标记方法,先插入一个Account数据 ;
然后调用下游服务 usercent 插入一条数据;

然后 上游服务 coredata继续执行业务逻辑,继续插入数据;

接着模拟上游服务coredata开始报错(我们通过name长度故意触发错误);

期望结果:  上下游两个服务 在当前方法事务下插入的数据都回滚!

上游服务 coredata 方法:

通过fegin调用下游服务 usercent 方法:

下游服务 usercent 的插入方法:

开始模拟:

1.先把eureka跑起来:

暂时就注册中心自己,没有别的服务:

2.把seata server 服务跑起来,注册到eureka上去:

我是window环境,执行.bat

可以看到已经成功注册到eureka上了:

3. 把上游微服务 coredata 和 下游服务 usercent 都跑起来:

然后看下seata server上,也可以看到 两个服务 都成功‘注册’到了 seata server上了,而且都在同一个事务组 test_tx_group里面:

接下来,就是开始调用一下我们模拟的场景代码就完事了(不过我会加点图来给大家简单分析下):

一开始,都是没有数据的:

调用上游服务coredata接口触发一下整个流程:

调用开始:

我们打断点到 上游已经插入过一次数据,下游也插入过一次数据,

可以看到上游服务和下游服务的数据库里面的undo_log表出现了 事务记录,有关当前事务的 branch_id和 所在事务 xid,而且可以看到在两个服务内的undo_log表中记录的xid都是一致的,代表他们都在一个事务中:

然后我们继续往下执行,故意让上游报错:

这时候,接口调用完毕结束:
我们看到seata server里面的信息,可以看到全局事务 xid为22080结尾,回滚成功:

回滚成功后,undo_log表中的记录会删除掉:


当然我们两个服务里面也是没有数据的,因为回滚了:

这里可能有人会想,你查一下是空就能证明是回滚了么? 

这时候我们也可以利用主键自增当前值可以看到确实发生了数据回滚的场景:

第一个场景就到此吧。

接下来我们模拟第二个场景:

上游服务 coredata 使用seata全局事务注解@GlobalTransactional 标记方法,先插入一个Account数据 ;
然后调用下游服务 usercent 插入一条数据;

然后下游服务 直接模拟出错, 这样触发事务回滚。

期望结果:  上下游两个服务 在当前方法事务下插入的数据都回滚!

也就是说我们需要对下游服务的插入方法里面做手脚,故意抛出错误:

快速调用一下:

可以看到下游出错了:

看下我们的seata server怎么说,已出发分布式事务,回滚成功:

数据库里面的数据也是回滚了,空的: 

好了,该篇springboot cloud使用eureka整合  分布式事务组件  Seata  就到此吧。



ps: 最近比较忙,每篇文章其实都是用一些零碎时间拼凑出来的。不过我会坚持我的文章的初衷,能让大家跟着实践,能搞懂,能学会。
我...只是个散工。

  • 14
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论
基于springcloud+springboot+nacos+openFeign的分布式事务组件seata项目源码.zip 介绍 分布式事务组件seata的使用demo,AT模式、TCC模式,集成springboot、springcloud(nacos注册中心、openFeign服务调用、Ribbon负载均衡器)、spring jpa,数据库采用mysql demo中使用的相关版本号,具体请看代码。如果搭建个人demo不成功,验证是否是由版本导致,版本稍有变化可能出现相关组件的版本不一致便会出现许多奇怪问题 seata服务端 1.3 Nacos服务端 1.1.4 spring-cloud-alibaba-dependencies 2.1.0.RELEASE springboot 2.1.3.RELEASE springcloud Greenwich.RELEASE 软件架构 软件架构说明 springcloud-common 公共模块 springcloud-order-AT 订单服务 springcloud-product-AT 商品库存服务 springcloud-consumer-AT 消费调用者 springcloud-business-Tcc 工商银行服务 springcloud-merchants-Tcc 招商银行服务 springcloud-Pay-Tcc 消费调用者 AT模式:springcloud-order-AT,springcloud-product-AT,springcloud-consumer-AT为AT模式Dome;模拟场景用户购买商品下单; 调用流程springcloud-consumer-AT调用订单服务创建订单(新增一条数据到订单表);在调用商品库存服务扣减商品库存数量(修改商品库存表商品数量);最后出现异常则统一回滚,负责统一提交; 第一阶段:准备阶段(prepare)协调者通知参与者准备提交订单,参与者开始投票。协调者完成准备工作向协调者回应Yes。 第二阶段:提交(commit)/回滚(rollback)阶段协调者根据参与者的投票结果发起最终的提交指令。如果有参与者没有准备好则发起回滚指令。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小目标青年

对你有帮助的话,谢谢你的打赏。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值