springboot2.x+dubbo2.x+seata1.x AT+nacos+zk实现分布式事务
Posted yangzongzhuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot2.x+dubbo2.x+seata1.x AT+nacos+zk实现分布式事务相关的知识,希望对你有一定的参考价值。
Seata AT模式接入
目录
4. 修改conf/ registry.conf配置... 10
5. 修改conf/ nacos-config.sh文件... 11
6. 执行conf/ nacos-config.sh脚本... 12
一、Seata AT模式介绍
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。本文只讲如何接入AT模式。
前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
写隔离
- 一阶段本地事务提交前,需要确保先拿到 全局锁 。
- 拿不到 全局锁 ,不能提交本地事务。
- 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。
读隔离
在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。
工作机制
以一个示例来说明整个 AT 分支的工作过程。
业务表:product
Field | Type | Key |
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
AT 分支事务的业务逻辑:
updateproduct
setname
=
'GTS'where
name
=
'TXC';
一阶段
过程:
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
selectid
,
name, since
fromproduct
wherename
=
'TXC';
得到前镜像:
id | name | since |
1 | TXC | 2014 |
- 执行业务 SQL:更新这条记录的 name 为 'GTS'。
- 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
selectid
,
name, since
fromproduct
whereid
=
1;
得到后镜像:
id | name | since |
1 | GTS | 2014 |
- 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到
UNDO_LOG
表中。
"branchId"
:
641789253,
"undoItems"
: [
"afterImage"
:
"rows"
: [
"fields"
: [
"name"
:
"id",
"type"
:
4,
"value"
:
1
,
"name"
:
"name",
"type"
:
12,
"value"
:
"GTS"
,
"name"
:
"since",
"type"
:
12,
"value"
:
"2014"
]
],
"tableName"
:
"product"
,
"beforeImage"
:
"rows"
: [
"fields"
: [
"name"
:
"id",
"type"
:
4,
"value"
:
1
,
"name"
:
"name",
"type"
:
12,
"value"
:
"TXC"
,
"name"
:
"since",
"type"
:
12,
"value"
:
"2014"
]
],
"tableName"
:
"product"
,
"sqlType"
:
"UPDATE"
],
"xid"
:
"xid:xxx"
- 提交前,向 TC 注册分支:申请
product
表中,主键值等于 1 的记录的 全局锁 。 - 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
- 将本地事务提交的结果上报给 TC。
二阶段-回滚
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理。
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
updateproduct
setname
=
'TXC'where
id
=
1;
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
优势:
业务代码无侵入。
保证事务隔离性。
DB模式支持高可用
缺点:
全局锁在高并发情况下可能有一定的性能问题
二、Seata AT接入步骤
用例描述
以user服务和mot服务为例,用户在平台提交删除空间的请求,admin-web作为事务发起方,同时调用user服务删除空间接口和mot服务删除mot策略器接口。
假设此处需要保证一致性,既要么同时失败,要么同时成功,那么此时接口的事务操作需要交给seata来处理。
下面来简单快速的讲一下如何接入seata来管理分布式事务。
框架组件
1. Springboot2.x
2. seata1.4
3. nacos:配置中心
4. zookeeper: 注册中心
5. dubbo2.x
配置Seata服务端
1. 下载seata-server插件
下载地址:https://github.com/seata/seata/releases/
解压后如下图
2. 配置config.txt
config.txt文件需要去github手动下载后放到seata根目录下,和config、bin等文件夹同级
https://github.com/seata/seata/blob/develop/script/config-center/config.txt
下载完毕后需要修改如下几项配置
store.mode=db #db模式高可用
#数据库配置
store.db.datasource=druid
store.db.db-type=mysql
store.db.driver-class-name=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://ip:3306/seata
store.db.user=用户名
store.db.password=密码
#事务组配置,新增或修改
service.vgroupMapping.user-service-group=default
service.vgroupMapping.mot-service-group=default
service.vgroupMapping.admin-service-group=default
#seata-server服务ip端口
service.default.grouplist=127.0.0.1:8091#seata-server服务的ip端口
db模式下的所需的三个表的数据库脚本位于
https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql
注意: 配置文件末尾有空行,需要删除,否则会提示失败,尽管实际上是成功的
3. 新建logs文件
在seata根目录新建文件夹logs,并在新建的logs文件夹中新建seata_gc.log文件,不然启动seata-server会报错。
最终目录结构如下
4. 修改conf/ registry.conf配置
registry #注册中心 # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "zk" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10
zk cluster = "default" serverAddr = "10.11.1.62:2181" sessionTimeout = 6000 connectTimeout = 2000
config #配置中心 # file、nacos 、apollo、zk、consul、etcd3 type = "nacos"
nacos #此处不要加http(s),不然会启动失败,非域名可以直接写IP端口xxx.xx.xx.x:8848 serverAddr = "nacos.com" namespace = "" #此处不填写,会自动写入到public空间下 group = "SEATA_GROUP" username = "用户名" password = "密码"
|
5. 修改conf/ nacos-config.sh文件
文件需要自行去GitHub下载,因为我这里配置中心用的是nacos所以下载的nacos目录下的shell脚本,其他的请自行选择。
https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
此处要注意,下载文件并放入conf目录后,需要进去修改nacos配置写入的接口地址
因为这里默认认为你的nacos是在本地。
非域名地址也可以在启动sh脚本是使用 -h -p参数手动设置变量
更改后
6. 执行conf/ nacos-config.sh脚本
执行脚本的目的是将config.txt中的配置写入到nacos中,后续seata-server启动需要去nacos读取配置
Windows下执行
在conf/文件中,利用git客户端的Git Bash Here插件开启面板,使用./ nacos-config.sh执行即可。
成功后
Linux下执行
直接执行./ nacos-config.sh 命令即可
NACOS效果
7. 启动seata-server服务
Windows下
进入bin/目录,双击seata-server.bat文件即可。
Linux下
进入bin/目录,执行./seata-server.sh 命令即可。
下面窗口,表示启动成功
到此seata服务端就处理完毕了,接下来开始处理客户端,即我们的项目工程!!!!
配置Seata客户端
Seata 客户端就是我们的项目工程,我这里三个服务都是已经架构成熟的,基于
springboot2.x+dubbo2.x+mybatis-plus+nacos(配置)+zk(注册)框架的服务。所以一些基础的搭建
过程我就不再赘述,只会讲一些关于seata的增量的东西。
如果是从0开始的项目,可以先了解如何搭建一套上述架构的系统,再来按照本文接入SEATA。
服务介绍
mot:会员营销服务
user-control:空间管理服务
admin-web: API服务
业务架构
分布式事务管理
mot服务
新建undo_log表
每个dubbo服务对应的数据库都需要新建这张表,admin-web不用,因为他没有直接操作数据库。
建表脚本:
https://github.com/seata/seata/blob/develop/script/client/at/db/mysql.sql
-- for AT mode you must to init this sql for you business database. the seata server not need it. CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id', `xid` VARCHAR(100) 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 = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table'; |
提供dubbo服务
写一个简单的新增标签接口供admin-web调用
IMotLabelSerivce.java
public int addLabel(LabelDTO labelDto); |
MotLabelSerivceImpl.java
@Override public int addLabel(LabelDTO labelDto) motLabelMapper.insert(labelDto); |
配置POM.XML文件
1. 使用seata-spring-boot-starter自动装配数据库代理
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<vsersion>1.4.0</version>
</dependency>
2. 引入zkclient
这一步看你工程具体架构设计,我这里需要加这个包,注意一点,引入这个包需要排除slf4j-log4j12,不然会冲突。
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
Seata基础配置
这里有两种方案,一是直接在applicaton.yml中写入,另外是在nacos配置中心,新增一个配置文件,我这里选的第二种
第一种:直接写入本地application.yml中
seata: enabled: true application-id: mot tx-service-group: mot-service-group #事务群组(对应conf/config.txt中配置service.vgroupMapping.mot-service-group=defaulf的后半部分名称) client: rm: lock: retry-interval: 10 retry-times: 30 retry-policy-branch-rollback-on-conflict: true report-retry-count: 5 async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000) table-meta-check-enable: false report-success-enable: true tm: commit-retry-count: 3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1) rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1) undo: log-table: undo_log # 自定义undo表名(默认undo_log) data-validation: true # 二阶段回滚镜像校验(默认true开启) log-serialization: jackson # undo序列化方式(默认jackson) only-care-update-columns: true log: exceptionRate: 100 # 日志异常输出概率(默认100) enable-auto-data-source-proxy: true#seata开启数据库代理 service: vgroup-mapping: name: default # TC 集群(必须与seata-server保持一致) enable-degrade: false # 降级开关 disable-global-transaction: false # 禁用全局事务(默认false) grouplist: ip: 127.0.0.1,port: 8091 #seata-server服务的IP端口 transport: shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker server-executor-thread-prefix: NettyServerBizHandler share-boss-worker: false client-selector-thread-prefix: NettyClientSelector client-selector-thread-size: 1 client-worker-thread-prefix: NettyClientWorkerThread type: TCP server: NIO heartbeat: true serialization: seata compressor: none enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送(默认true) registry: type: zk zk: cluster: default server-addr: 10.11.1.62:2181 session-timeout: 6000 config: type: nacos nacos: namespace: server-addr: https://nacos-test.01member.com #这里的地址如果是https的需要写死在这里,不然获取配置时,会自动使用http协议 password: axGgTRUopxXbXTMATbU4E29o username: nacos group: SEATA_GROUP |
第二种:使用nacos配置中心
Step1 在私有空间中新增配置文件
dataId: seata.yml
group: mot
输入文件内容,点击发布即可
seata: enabled: true application-id: mot tx-service-group: mot-service-group # 事务群组(可以每个应用独立取名,也可以使用相同的名字) client: rm: lock: retry-interval: 10 retry-times: 30 retry-policy-branch-rollback-on-conflict: true report-retry-count: 5 async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000) table-meta-check-enable: false report-success-enable: true tm: commit-retry-count: 3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1) rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1) undo: log-table: undo_log # 自定义undo表名(默认undo_log) data-validation: true # 二阶段回滚镜像校验(默认true开启) log-serialization: jackson # undo序列化方式(默认jackson) only-care-update-columns: true log: exceptionRate: 100 # 日志异常输出概率(默认100) enable-auto-data-source-proxy: true #seata接管数据库代理 service: vgroup-mapping: name: default # TC 集群(必须与seata-server保持一致) enable-degrade: false # 降级开关 disable-global-transaction: false # 禁用全局事务(默认false) grouplist: ip: 127.0.0.1,port: 8091 transport: shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker server-executor-thread-prefix: NettyServerBizHandler share-boss-worker: false client-selector-thread-prefix: NettyClientSelector client-selector-thread-size: 1 client-worker-thread-prefix: NettyClientWorkerThread type: TCP server: NIO heartbeat: true serialization: seata compressor: none enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送(默认true) registry: type: zk zk: cluster: default server-addr: 10.11.1.62:2181 session-timeout: 6000 config: type: nacos nacos: namespace: server-addr: https://nacos-test.01member.com password: axGgTRUopxXbXTMATbU4E29o username: nacos group: SEATA_GROUP |
Step2 在applicationContext-nacos.xml文件中配置seata.yml
<!-- seata分布式事务配置 -->
<nacos:property-source data-id="seata.yml" group-id="$spring.application.name" auto-refreshed="true"/>
启动MOT服务
执行ScrmMotApplication.java的main方法后,查看seata-server服务窗口,成功后如下图
(使用鼠标滚轮扩大后更清晰)
user-control服务
新建undo_log表
每个dubbo服务对应的数据库都需要新建这张表,admin-web不用,因为他没有直接操作数据库。
建表脚本:
https://github.com/seata/seata/blob/develop/script/client/at/db/mysql.sql
-- for AT mode you must to init this sql for you business database. the seata server not need it. CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT(20) NOT NULL COM 以上是关于springboot2.x+dubbo2.x+seata1.x AT+nacos+zk实现分布式事务的主要内容,如果未能解决你的问题,请参考以下文章 Alibaba微服务技术系列「Dubbo3.0技术专题」回顾Dubbo2.x的技术原理和功能实现 SpringBoot2.X最佳实践《一》 之 SpringBoot2.x初体验 小D课堂 - 零基础入门SpringBoot2.X到实战_汇总 2019刘老师教你用springboot2.x开发整合微信支付的线上教育平台带源码送springboot2.x零基础入门到高级实战教程 |