SpringCloud-Alibaba-Seata解决分布式事务
Seata是Alibaba开源的一款分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,本文将通过一个简单的下单业务场景来对其用法进行详细介绍。
什么是分布式事务问题?
单体应用
单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。
微服务应用
随着业务需求的变化,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
小结
在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,一次业务操作需要操作多个数据源或需要进行远程调用,就会产生分布式事务问题。
Seata简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
目前已支持 Dubbo、Spring Cloud、Sofa-RPC、Motan 和 grpc 等RPC框架,其他框架持续集成中
Seata原理和设计
定义一个分布式事务
我们可以把一个分布式事务理解成一个包含了若干分支事务的全局事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足ACID的本地事务。这是我们对分布式事务结构的基本认识,与 XA 是一致的。
协议分布式事务处理过程的三个组件
- Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
- Transaction Manager ™:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
- Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
一个典型的分布式事务过程
- TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
- XID 在微服务调用链路的上下文中传播;
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
- TM 向 TC 发起针对 XID 的全局提交或回滚决议;
- TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
seata-server的安装与配置
使用环境CentOS 7 + java 8
seata1.4.0 (关键配置和缺失的文件都已经补全)
-
链接:https://pan.baidu.com/s/1JHO1xWlk2ZO1gf88rUgYdA
提取码:1234
然后在CentOS 7 /usr/src/ 目录下解压文件
nuzip -o seata1.4.0.zip
然后其他具体步骤如下:
- 初始化seata数据库
- 这里我们使用Nacos作为注册中心和配置中心,Nacos的安装及使用可以参考:
Centos7-Nacos-服务注册中心搭建
- 将config.txt文件内的配置推送到nacos配置中心中
- 修改
目录下的conf
配置文件registry.conf
- 启动seata将服务注册到Nacos中
初始化数据库
创建数据库seata然后导入下面这3张表
-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `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`)
);
-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
`branch_id` bigint not null,
`xid` varchar(128) not null,
`transaction_id` bigint ,
`resource_group_id` varchar(32),
`resource_id` varchar(256) ,
`lock_key` varchar(128) ,
`branch_type` varchar(8) ,
`status` tinyint,
`client_id` varchar(64),
`application_data` varchar(2000),
`gmt_create` datetime,
`gmt_modified` datetime,
primary key (`branch_id`),
key `idx_xid` (`xid`)
);
-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
`row_key` varchar(128) not null,
`xid` varchar(96),
`transaction_id` long ,
`branch_id` long,
`resource_id` varchar(256) ,
`table_name` varchar(32) ,
`pk` varchar(36) ,
`gmt_create` datetime ,
`gmt_modified` datetime,
primary key(`row_key`)
);
在每一个需要分布式事务的业务数据库中都需要添加下面这表:
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
将config.txt文件内的配置推送到nacos配置中心中
cd /usr/src/seata
修改config.txt内容
store.mode=db
...........................................
store.db.url=jdbc:mysql://192.168.0.102:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
一定要把数据库的ip改为真实ip 而不是127.0.0.1
其余的默认就行
然后进入到 cd /usr/src/seata/conf 执行
sh nacos-config.sh -h 192.168.81.129 -p 8848 -g SEATA_GROUP -u nacos -w nacos
执行完后应该会打印
=========================================================================
Complete initialization parameters, total-count:94 , failure-count:4
=========================================================================
参数解释
-h nacos注册中心地址
-p nacos注册中心端口
-g nacos中注册的分组(默认 SEATA_GROUP)
-u nacos注册中心账号
-w nacos注册中心密码
修改registry.conf文件
cd /usr/src/seata/conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "192.168.81.129:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
..............................................
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "192.168.81.129:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
然后进入
cd /usr/src/seata/bin
seata-server.sh默认JVM配置是
-server -Xmx2048m -Xms2048m -Xmn1024m -Xss512k
一般测试机器没有这么大的内容所以我们需要改小点 找到大约120行左右位置,改为
-server -Xmx512m -Xms512m -Xmn256m -Xss256k
然后执行:
chmod +x ./seata-server.sh
nohup ./seata-server.sh -h 192.168.81.129 -p 8092 &
- h : 指定Seata服务器的主机IP
- p : 指定Seata应用的端口号
如果查看日志看打印的最后一行结果类似如下
xxxx… Server started, listen port: 8092
那么就代表seata配置启动成功,剩下的就该项目如何进行使用了
如果需要查看和关闭项目
项目中使用分布式事务
项目结构
下面只写关键代码写,其他文件其实在以前的教程中都学过了,会不会都无关紧要
Maven
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring.cloud.alibaba.version>2.2.3.RELEASE</spring.cloud.alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Springboot 版本要高于 spring-cloud-alibaba 否则报错-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<dependencies>
<!-- spring-cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- web服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.1</version>
</dependency>
<!-- springboot test启动组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.1.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- 将服务注册到Nacos里-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Mybati 和Spring boot 自动整合依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--数据库驱动 告诉Springboot 我们使用mysql数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- 分布式事务 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!-- 排除依赖 指定版本和服务器端一致 -->
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
主项目: nacos-seata1
application.yml
server:
port: 10601
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/voidme?characterEncoding=UTF-8
application:
name: nacos-seata1
cloud:
nacos:
discovery:
server-addr: 192.168.81.129:8848 #注册中心地址 我使用nginx做了集群代理了
mybatis:
type-aliases-package: com.springbootalibaba.nacos.pojo
mapper-locations: classpath:mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
seata:
enabled: true # 默认就行
tx-service-group: my_test_tx_group #默认分组就是 my_test_tx_group这个可以不要动
enable-auto-data-source-proxy: true # 默认就行
registry:
type: nacos # 默认就行
nacos:
server-addr: 192.168.81.129:8848 # 改为自己的Nacos
# namespace: seata_namespace_id
group: SEATA_GROUP # 默认就行
config:
type: nacos # 默认就行
nacos:
server-addr: 192.168.81.129:8848 # 改为自己的Nacos
# namespace: seata_namespace_id
group: SEATA_GROUP
SeataApplication
package com.seata1;
import io.seata.spring.annotation.datasource.EnableAutoDataSourceProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoDataSourceProxy //必须添加否则seata分布式事务无效
public class SeataApplication {
public static void main(String[] args) {
SpringApplication.run(SeataApplication.class,args);
}
}
UserController
package com.seata1.controller;
import com.seata1.dao.UserMapper;
import com.seata1.pojo.User;
import com.seata1.utils.RestTemplateUtils;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("seata1")
public class UserController {
@Autowired
private RestTemplateUtils restTemplateUtils;
//http://localhost:10601/seata1/save?id=3 正确
// http://localhost:10601/seata1/save?id=0 触发事务
@Autowired
private UserMapper userMapper;
@GetMapping("/save")
@GlobalTransactional(rollbackFor = Exception.class) //开启分布式事务
public String save(@RequestParam("id") Integer id){
User build = User.builder().name("你媽11111").age(22).sex("女").build();
userMapper.save(build); //添加数据
restTemplateUtils.delete("http://nacos-seata2/seata2/delete?id="+id,String.class); //调用远程服务的删除数据
User build1 = User.builder().id(4).name("xx").age(21).sex("男").build();
userMapper.update(build1); //修改数据
return "success";
}
}
副项目: nacos-seata2
application.yml
基本配置和主项目一样 把端口换下就行了
server:
port: 10602
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/voidme?characterEncoding=UTF-8
application:
name: nacos-seata2
cloud:
nacos:
discovery:
server-addr: 192.168.81.100:80 #注册中心地址
mybatis:
type-aliases-package: com.springbootalibaba.nacos.pojo
mapper-locations: classpath:mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
seata:
enabled: true
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
registry:
type: nacos
nacos:
server-addr: 192.168.81.100:80
# namespace: seata_namespace_id
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: 192.168.81.100:80
# namespace: seata_namespace_id
group: SEATA_GROUP
SeataApplication
package com.seata1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableDiscoveryClient
public class SeataApplication {
public static void main(String[] args) {
SpringApplication.run(SeataApplication.class,args);
}
}
UserController
package com.seata1.controller;
import com.seata1.dao.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("seata2")
public class UserController {
//http://localhost:10602/seata2/delete?id=1
@Autowired
private UserMapper userMapper;
@DeleteMapping("/delete")
public String delete(@RequestParam("id") Integer id)
{
if (id==0) {
int i=1/0;
}
return userMapper.delete(id).toString();
}
}
测试
http://localhost:10601/seata1/save?id=3 正确
http://localhost:10601/seata1/save?id=0 触发事务,所有服务接口都回滚操作
点赞 -收藏加 -关注 便于以后复习和收到最新内容 有其他问题在评论区讨论-或者私信我-收到会在第一时间回复 感谢,配合,希望我的努力对你有帮助^_^