什么是分布式系统cap定理?
一致性:如果我部署了多个相同的服务,用户访问所有的服务,拿到的数据都是一样的;
可用性:比如说我们设置一个合理时间为1秒,如果用户访问这个接口超过一秒,那就不是可用性;
分区容忍度(容错性):当出现通信故障时,能正常提供服务;
一致性分类;
强一致性:数据必须完全一致,中间过程不可见,同步完成,意思就是张三和李四转账,钱必须完全一致,其他人在进行给他俩转账的时候阻塞,等待他俩完成才能进行;
弱一致性:允许部分一致性,中间过程可见,异步完成,意思是系统给A,B,C三个服务异步发送消息,我访问A服务和B服务看到的数据是一样的,看到C服务的数据是不一样的;
最终一致性:过一段时间后,保证数据完全一致;
分布式解决方案JTA(XA)
准备阶段:业务处理,但不会提交数据,当处理完成之后,进入提交阶段;
提交阶段:如果准备阶段出现错误,那么提交阶段立即回滚,如果没有出现错误,那么提交成功;
JTA就是采用2个阶段进行事物的处理;
XA是一个分布式事物的规范,采用两阶段方案(Pre,Commit);
TCC分布式事物解决方案?
TCC是try-尝试,confirm-确认,cancel-取消;
try尝试阶段,对资源进行锁定;
confirm确认阶段,对资源进行确认,完成操作;
cancel取消阶段,对资源进行还原,取消操作;
try尝试阶段:创建预增字段,写入预增数据;
confirm确认阶段:把预增数据,写入真实的字段中;
cancel取消阶段:当出现报错,把真实字段的数据进行还原;
seata分布式事物解决方案?
seata为用户提供了AT,TCC,SAGA 三种事物模式
seata的AT事物模式?
TC:事物协调者,维护全局和分支事物的状态,驱动全局事物提交和回滚;
TM:事物管理器,用于定义全局事物的范围,开始全局事物提交或者回滚全局事物;
RM:资源管理器,用于管理分支事物处理的资源,与TC进行数据的交换,并且报告分支事物的状态,并驱动分支事物提交或者回滚;
TM事物管理器,管理会员采购这个核心方法,这个方法里面包含订单服务的创建订单方法,积分服务的增加积分方法,库存服务的减少库存方法;
RM资源管理器对应不同的服务,订单服务,创建订单,积分服务,增加积分,库存服务,减少库存;每个服务都有自己的RM资源管理器
Seata-server会提供一个TC的事物协调者;
TM开启一个TC的全局事物请求,TM中的方法里面,调用不同服务的的方法,方法执行成功后,每个服务添加一份数据到undo_log中,
然后RM会向TC上报一个处理结果,通知TC我这个RM处理成功了。
当TM管理的核心方法,全部处理完成之后,向TC发送一个全局提交的请求,一旦TC收到这个请求,表示所以操作都完成了,通知RM,并把undo_log删除;
当TM管理的核心方法,有一个报错了,向TC发送一个全局回滚的请求,然后TC通知每一个RM,进行回滚数据;
接下来下载
https://github.com/seata/seata/releases/download/v1.6.1/seata-server-1.6.1.zip
seata的控制台
账号seata,密码seata
把注册中心和配置中心都改成nacos
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
store:
# support: file 、 db 、 redis
mode: file
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
然后启动seata
在nacos就能看到注册上了
进入这个目录
在mysql创建一个数据库名字叫seata,导入这个mysql.sql
进入下面的目录,编辑config.txt文件
修改成你自己的ip
store.mode=db,数据库改成你的
这几个地方给他加上双引号,要不然执行失败
然后 在这个目录右键 git bash here ,前提是你先安装好git
执行下面的命令
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -u nacos -w nacos
可以看到nacos已经有配置信息了
然后我们在启动seata
在每一个业务数据库创建回滚日志表
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 DEFAULT CHARSET=utf8;
在订单数据库创建订单表
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在库存数据库创建库存表
CREATE TABLE `kucun` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT NULL COMMENT '库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入库存数据
INSERT INTO `kucun` (`id`, `name`, `count`) VALUES ('2', '北京库存', '10');
接下来创建springcloud项目
创建库存项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>kc2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>kc2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--这个web包一定要有,否则项目启动不起来-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加MyBatisPlus的依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- MySQL数据 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- druid 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.0</version>
</dependency>
<!--这个包解决tc向rm发送回滚指令-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.kc2.Kc2Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
这是seata的核心包
package com.example.kc2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
//排除自带的数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class Kc2Application {
public static void main(String[] args) {
SpringApplication.run(Kc2Application.class, args);
}
}
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
server:
port: 8089
spring:
application:
name: kc2
cloud:
nacos:
discovery:
namespace: public
password: nacos
server-addr: localhost:8848
username: nacos
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://192.168.23.131:13309/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
seata:
enabled: true
#应用id
application-id: ${spring.application.name}
#事物组 service.vgroupMapping.default_tx_group
tx-service-group: default_tx_group
#这里设置false 使用自定义的代理数据源配置,这样就可以解决mapper找不到的问题
enable-auto-data-source-proxy: false
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
service:
vgroupMapping:
default_tx_group: default
logging:
level:
io:
seata: debug
package com.example.kc2.service;
public interface KuCunService {
public void update();
}
package com.example.kc2.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.kc2.entity.KuCun;
import com.example.kc2.mapper.KuCunMapper;
import com.example.kc2.service.KuCunService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class KuCunServiceImpl implements KuCunService {
@Autowired
private KuCunMapper kuCunMapper;
@Override
public void update() {
KuCun kuCun=kuCunMapper.selectById(2);
kuCun.setCount(10);
kuCunMapper.updateById(kuCun);
}
}
package com.example.kc2.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.kc2.entity.KuCun;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface KuCunMapper extends BaseMapper<KuCun> {
}
package com.example.kc2.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("kucun")
public class KuCun {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer count;
}
package com.example.kc2.controller;
import com.example.kc2.service.KuCunService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class KuCunController {
@Autowired
private KuCunService kuCunService;
@GetMapping("/update")
public void update(){
kuCunService.update();
}
}
package com.example.kc2.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan({"com.example.kc2.mapper"})
public class MyBatisConfig {
}
package com.example.kc2.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
//使用代理数据源
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
//使用io.seata.rm.datasource.DataSourceProxy
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
接下来创建订单项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>order</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--这个web包一定要有,否则项目启动不起来-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加MyBatisPlus的依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- MySQL数据 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- druid 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.0</version>
</dependency>
<!--这个包解决tc向rm发送回滚指令-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.order.OrderApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
server:
port: 8088
spring:
application:
name: order
cloud:
nacos:
discovery:
namespace: public
password: nacos
server-addr: localhost:8848
username: nacos
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://192.168.23.131:13310/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
seata:
enabled: true
#应用id
application-id: ${spring.application.name}
#事物组 service.vgroupMapping.default_tx_group
tx-service-group: default_tx_group
#这里设置false 使用自定义的代理数据源配置,这样就可以解决mapper找不到的问题
enable-auto-data-source-proxy: false
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
service:
vgroupMapping:
default_tx_group: default
logging:
level:
io:
seata: debug
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
//排除自带的数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
package com.example.order.service;
public interface OrderService {
public void addOrder();
}
package com.example.order.service.impl;
import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.service.OrderService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public void addOrder() {
Order order=new Order();
order.setOrderName("订单名称");
orderMapper.insert(order);
}
}
package com.example.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.order.entity.Order;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
package com.example.order.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("t_order")
public class Order {
@TableId(type = IdType.AUTO)
private Integer id;
private String orderName;
}
package com.example.order.controller;
import com.example.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/addOrder")
public void addOrder(){
orderService.addOrder();
}
}
package com.example.order.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan({"com.example.order.mapper"})
public class MyBatisConfig {
}
package com.example.order.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
//使用代理数据源
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
//使用io.seata.rm.datasource.DataSourceProxy
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
接下来我们在创建业务项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>yewu</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>yewu</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--这个web包一定要有,否则项目启动不起来-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加MyBatisPlus的依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- MySQL数据 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- druid 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--和其他服务进行通信-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.0</version>
</dependency>
<!--这个包解决tc向rm发送回滚指令-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.yewu.YewuApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
server:
port: 8087
spring:
application:
name: yewu
cloud:
nacos:
discovery:
namespace: public
password: nacos
server-addr: localhost:8848
username: nacos
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://192.168.23.131:13306/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
seata:
enabled: true
#应用id
application-id: ${spring.application.name}
#事物组 service.vgroupMapping.default_tx_group
tx-service-group: default_tx_group
#设置为false,使用自定义的代理数据源,解决mapper找不到的问题
enable-auto-data-source-proxy: false
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group : "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
service:
vgroupMapping:
default_tx_group: default
#打印seata日志
logging:
level:
io:
seata: debug
package com.example.yewu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
//排除自带的数据源配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class YewuApplication {
public static void main(String[] args) {
SpringApplication.run(YewuApplication.class, args);
}
}
package com.example.yewu.service;
public interface TestService {
public void test();
}
package com.example.yewu.service.impl;
import com.example.yewu.feign.KcFeign;
import com.example.yewu.feign.OrderFeign;
import com.example.yewu.service.TestService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TestServiceImpl implements TestService {
@Autowired
private KcFeign kcFeign;
@Autowired
private OrderFeign orderFeign;
/**
* 开启seata的全局事物
* name:事物名
* timeoutMills:超时时间 超过时间回滚
*
* @param
* @return
* @throws Exception
*/
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public void test() {
//调用库存服务
kcFeign.update();
//调用订单服务
orderFeign.addOrder();
//模拟报错
//int i=1/0;
// throw new RuntimeException("aaaaaaaaaa");
}
}
package com.example.yewu.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
//名称就是在nacos注册的微服务的名字
@FeignClient(value = "order")
public interface OrderFeign {
@GetMapping("/addOrder")
public void addOrder();
}
package com.example.yewu.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
//名称就是在nacos注册的微服务的名字
@FeignClient(value = "kc2")
public interface KcFeign {
@GetMapping("/update")
public void update();
}
package com.example.yewu.controller;
import com.example.yewu.feign.KcFeign;
import com.example.yewu.feign.OrderFeign;
import com.example.yewu.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@Autowired
private TestService testService;
@GetMapping("/test")
public void test(){
testService.test();
}
}
package com.example.yewu.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan({"com.example.yewu.mapper"})
public class MyBatisConfig {
}
package com.example.yewu.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
//使用代理数据源
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
//使用io.seata.rm.datasource.DataSourceProxy
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
接下来我们演示下正常的效果
库存是100
订单是空
启动3个项目和seata,nacos
当RM(资源管理器)启动成功之后,就是我们的库存,订单项目
会在TC(事物协调者),就是seata-server上面 显示,TM注册成功,和RM注册成功
当项目关闭之后,会从通道中移除
如果seata的cmd长期没有反应,那么按下回车
接下来我们测试一把成功的
可以看到处理成功
我们可以在TC看到,开启一个新的全局事物,注册2个分支,最后提交全局事物
全局事物就是靠这个注解,默认超时时间是60秒
@GlobalTransactional(rollbackFor = Exception.class)
我们在在数据还原,并把这里的注释打开测试一下回滚
可以在TC看到,开启一个全局事物,注册2个分支,回滚2个分支,回滚全局事物
可以看到,库存和订单已经回滚成功
之前在事物回滚的时候发现失效了,我在就库存和订单搜索rollback
发现是空的,并且xid也是null
那么我就在pom里面加入了这个包,最终解决事物回滚失效,导致tc无法发送给rm指令
如果遇到了其他的问题,请到官网去看相应的问题
http://seata.io/zh-cn/docs/overview/faq.html
AT模式执行解析;
1.业务方法准备运行-TM向TC发起全局事物,生成xid(全局锁);
2.库存方法-写表,undo_log纪录回滚日志,生成branch_id(分支id,每一个分支id都不一样),通知TC操作结果;
3.订单方法-写表,undo_log纪录回滚日志,生成branch_id,通知TC操作结果;
4.业务方法-执行成功,TM通知TC全局提交;
5.TC通知所有RM提交成功,删除undo_log回滚日志;
6.业务方法-执行失败,TM通知TC全局回滚;
7.TC通知所有RM进行回滚,RM通过undo_log进行回滚数据,恢复后,删除undo_log;
这样就保证了我们的最终一致性;
接下来我们看下undo_log到底长什么样,把下面的代码设置一下超时时间
我们把undo_log数据拿出来
可以看到开启的数据是库存100
修改过后的数据是10,通过这些数据,我们就知道是怎么恢复了
使用场景:在老项目,新项目都可以使用,一般都是使用的AT模式
接下来看下TCC事物模式
一阶段prepare(准备)行为:调用自己的准备方法;
二阶段commit(提交/确认)行为:调用自己的提交方法;
二阶段 rollback(回滚)行为:调用自己的回滚方法;
@LocalTCC标识为本地模式,本地调用事物,非远程调用,只要实现了两阶段提交方法,
都必须在该接口上面加这个注解;
TCC模式是不用undo_log的;
然后我们对sql进行改造,加入一些预增字段
在库存表加入冻结库存字段
CREATE TABLE `kucun` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT NULL COMMENT '库存',
`dong_jie` int(11) DEFAULT NULL COMMENT '冻结库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
在订单表加入状态字段
CREATE TABLE `t_order` (
`status` int(11) DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
接下来对订单项目改造
package com.example.order.service;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
//本地tcc
@LocalTCC
public interface OrderService {
public void addOrder();
/**
* 准备阶段
* TwoPhaseBusinessAction 两阶段业务操作
* commitMethod 提交确认方法
* rollbackMethod 回滚方法
* name tcc的bean名称,全局唯一
* BusinessActionContextParameter 可以把参数传递给commitMethod,rollbackMethod方法,
* 通过BusinessActionContext来获取
* @param
* @return
* @throws Exception
*/
@TwoPhaseBusinessAction(name = "prepare",commitMethod = "commit",rollbackMethod = "rollback")
boolean prepare(@BusinessActionContextParameter(paramName = "id")Integer id);
/**
*
* 提交阶段
* @param
* @return
* @throws Exception
*/
boolean commit(BusinessActionContext context);
/**
*
* 回滚阶段
* @param
* @return
* @throws Exception
*/
boolean rollback(BusinessActionContext context);
}
package com.example.order.service.impl;
import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.service.OrderService;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public void addOrder() {
Order order=new Order();
order.setOrderName("订单名称");
orderMapper.insert(order);
}
//事物的传播行为,每次都创建一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean prepare(Integer id) {
Order bean = orderMapper.selectById(id);
if(bean!=null){
//防止重复数据
return true;
}
Order order=new Order();
order.setId(id);
order.setOrderName("订单名称");
//0 准备阶段 1 完成
order.setStatus(0);
orderMapper.insert(order);
return true;
}
//事物的传播行为,每次都创建一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean commit(BusinessActionContext context) {
int id=Integer.parseInt(context.getActionContext("id").toString());
Order order = orderMapper.selectById(id);
if(order==null){
//防止空提交
return true;
}
//0 准备阶段 1 完成
order.setStatus(1);
orderMapper.updateById(order);
log.info("事物xid:"+context.getXid()+",提交成功");
return true;
}
//事物的传播行为,每次都创建一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean rollback(BusinessActionContext context) {
int id=Integer.parseInt(context.getActionContext("id").toString());
Order order = orderMapper.selectById(id);
if(order==null){
//防止空回滚
return true;
}
//回滚数据
orderMapper.deleteById(id);
log.info("事物xid:"+context.getXid()+",回滚成功");
return true;
}
}
package com.example.order.controller;
import com.example.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/addOrder")
public void addOrder(){
orderService.prepare(10);
}
}
接下来对库存项目进行改造
package com.example.kc2.service;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
//本地TCC
@LocalTCC
public interface KuCunService {
public void update();
/**
* 准备阶段
* TwoPhaseBusinessAction 两阶段业务操作
* commitMethod 提交确认方法
* rollbackMethod 回滚方法
* name tcc的bean名称,全局唯一
* BusinessActionContextParameter 可以把参数传递给commitMethod,rollbackMethod方法,
* 通过BusinessActionContext来获取
* @param
* @return
* @throws Exception
*/
@TwoPhaseBusinessAction(name = "prepare",commitMethod = "commit",rollbackMethod = "rollback")
boolean prepare(@BusinessActionContextParameter(paramName = "id")Integer id);
/**
*
* 提交阶段
* @param
* @return
* @throws Exception
*/
boolean commit(BusinessActionContext context);
/**
*
* 回滚阶段
* @param
* @return
* @throws Exception
*/
boolean rollback(BusinessActionContext context);
}
package com.example.kc2.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.example.kc2.entity.KuCun;
import com.example.kc2.mapper.KuCunMapper;
import com.example.kc2.service.KuCunService;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
public class KuCunServiceImpl implements KuCunService {
@Autowired
private KuCunMapper kuCunMapper;
@Override
public void update() {
KuCun kuCun=kuCunMapper.selectById(2);
kuCun.setCount(10);
kuCunMapper.updateById(kuCun);
}
//事物的传播行为,每次都创建一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean prepare(Integer id) {
KuCun kuCun = kuCunMapper.selectById(id);
if(kuCun==null){
//防止空数据
return true;
}
//把数据先放到冻结库存里面
kuCun.setDongJie(10);
kuCunMapper.updateById(kuCun);
return true;
}
//事物的传播行为,每次都创建一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean commit(BusinessActionContext context) {
int id=Integer.parseInt(context.getActionContext("id").toString());
KuCun kuCun = kuCunMapper.selectById(id);
if(kuCun==null){
//防止空数据
return true;
}
//把冻结的库存放入真实库存
kuCun.setCount(kuCun.getDongJie());
//把冻结库存清空
kuCun.setDongJie(0);
kuCunMapper.updateById(kuCun);
log.info("事物xid:"+context.getXid()+",提交成功");
return true;
}
//事物的传播行为,每次都创建一个新的事物
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean rollback(BusinessActionContext context) {
int id=Integer.parseInt(context.getActionContext("id").toString());
KuCun kuCun = kuCunMapper.selectById(id);
if(kuCun==null){
//防止空数据
return true;
}
//重置库存
kuCun.setCount(100);
//把冻结库存清空
kuCun.setDongJie(0);
kuCunMapper.updateById(kuCun);
log.info("事物xid:"+context.getXid()+",回滚成功");
return true;
}
}
package com.example.kc2.controller;
import com.example.kc2.service.KuCunService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class KuCunController {
@Autowired
private KuCunService kuCunService;
@GetMapping("/update")
public void update(){
kuCunService.prepare(2);
}
}
接下来在访问
先做一些报错的数据,我们看下效果,可以看到数据都回滚了
在库存,订单项目可以看到
接下来我们弄1条成功的数据
可以看到,数据已经发送了变化
在库存,订单项目可以看到
在准备阶段,我们把数据放入预增字段;
在提交阶段,才把数据放入真正的字段;
在回滚的时候,才把数据进行恢复;
使用场景:在新项目中使用;
接下来我们看下Saga事物模式
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
说白了就是我创建订单的时候,要有一个取消订单的方法;
当前支付成功的时候,要有一个取消支付的方法;
一个成功的方法,一个失败的方法;
这个模式只做下了解,真正用的还是AT模式;
使用场景:业务流程长,业务流程多