Seata处理分布式事务
Seata
分布式事务问题
分布式前
单机单库没这个问题
从1:1 -> 1:N -> N: N
分布式之后
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
Seata简介
是什么
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
http://seata.io/zh-cn/
能干嘛
一个典型的分布式事务过程
分布式事务处理过程的-ID+三组件模型
Transaction ID XID 全局唯一的事务ID
3组件概念
Transaction Coordinator(TC)
事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager(TM)
控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager(RM)
控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;
处理过程
下载
发布说明:https://github.com/seata/seata/releases
怎么用
Spring 本地@Transactional
全局@GlobalTransactional
SEATA的分布式交易解决方案
Seata-Server安装
http://seata.io/zh-cn/
下载版本
seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件
先备份原始file.conf文件
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
file.conf
service模块
1
vgroup_mapping.my_test_tx_group = "fsp_tx_group"
store模块
1
2
3
4
5
6mode = "db"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "你自己的密码"
mysql5.7数据库新建库seata
在seata库里建表
建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面(1.0版本在readme.md里面)
db_store.sql
SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52-- 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`)
);
修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件
1
2
3
4
5
6
7
8
9
10registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
目的是:指明注册中心为nacos,及修改nacos连接信息先启动Nacos端口号8848
再启动seata-server
softs\seata-server-0.9.0\seata\bin
seata-server.bat
订单/库存/账户业务数据库准备
以下演示都需要先启动Nacos后启动Seata,保证两个都OK
Seata没启动报错no available server to connect
分布式事务业务说明
业务说明
下订单–>扣库存–>减账户(余额)
创建业务数据库
seata_order: 存储订单的数据库
seata_storage:存储库存的数据库
seata_account: 存储账户信息的数据库
建表SQL
1 | CREATE DATABASE seata_order; |
按照上述3库分别建对应业务表
seata_order库下建t_order表
1 | CREATE TABLE t_order( |
seata_storage库下建t_storage表
1 | CREATE TABLE t_storage( |
seata_account库下建t_account表
1 | CREATE TABLE t_account( |
按照上述3库分别建对应的回滚日志表
订单-库存-账户3个库下都需要建各自的回滚日志表
\seata-server-0.9.0\seata\conf目录下的db_undo_log.sql
建表SQL
1 | drop table `undo_log`; |
最终效果
订单/库存/账户业务微服务准备
业务需求
下订单->减库存->扣余额->改(订单)状态
新建订单Order-Module
seata-order-service2001
POM
1 | <dependencies> |
3.YML
1 | server: |
4.file.conf
1 | transport { |
5.registry.conf
1 | registry { |
6.domain
CommonResult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.kayleh.springcloud.alibaba.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
public class CommonResult<T>
{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}Order
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.kayleh.springcloud.alibaba.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
public class Order
{
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status; //订单状态:0:创建中;1:已完结
}
7.Dao接口及实现
OrderDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.kayleh.springcloud.alibaba.dao;
import com.kayleh.springcloud.alibaba.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
public interface OrderDao
{
//新建订单
void create(Order order);
//修改订单状态,从零改为1
void update(; Long userId, Integer status)
}resources文件夹下新建mapper文件夹后添加
OrderMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<mapper namespace="com.kayleh.springcloud.alibaba.dao.OrderDao">
<resultMap id="BaseResultMap" type="com.kayleh.springcloud.alibaba.domain.Order">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="user_id" property="userId" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="count" property="count" jdbcType="INTEGER"/>
<result column="money" property="money" jdbcType="DECIMAL"/>
<result column="status" property="status" jdbcType="INTEGER"/>
</resultMap>
<insert id="create">
insert into t_order (id,user_id,product_id,count,money,status)
values (null,#{userId},#{productId},#{count},#{money},0);
</insert>
<update id="update">
update t_order set status = 1
where user_id=#{userId} and status = #{status};
</update>
</mapper>
8.Service接口及实现
OrderService
1
2
3
4
5
6
7
8package com.kayleh.springcloud.alibaba.service;
import com.kayleh.springcloud.alibaba.domain.Order;
public interface OrderService{
void create(Order order);
}OrderServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55package com.kayleh.springcloud.alibaba.service.impl;
import com.kayleh.springcloud.alibaba.dao.OrderDao;
import com.kayleh.springcloud.alibaba.domain.Order;
import com.kayleh.springcloud.alibaba.service.AccountService;
import com.kayleh.springcloud.alibaba.service.OrderService;
import com.kayleh.springcloud.alibaba.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Sl f4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
public class OrderServiceImpl implements OrderService
{
private OrderDao orderDao;
private StorageService storageService;
private AccountService accountService;
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
*/
public void create(Order order){
log.info("----->开始新建订单");
//新建订单
orderDao.create(order);
//扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//修改订单状态,从零到1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了");
}
}
StorageService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.kayleh.springcloud.alibaba.service;
import com.kayleh.springcloud.alibaba.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
public interface StorageService{
CommonResult decrease(; Long productId, Integer count)
}AccountService
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com.kayleh.springcloud.alibaba.service;
import com.kayleh.springcloud.alibaba.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
public interface AccountService{
CommonResult decrease(; Long userId, BigDecimal money)
}
9.Controller
1 | package com.kayleh.springcloud.alibaba.controller; |
10.Config配置
MyBatisConfig
1
2
3
4
5
6
7
8
9
10
11package com.kayleh.springcloud.alibaba.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
public class MyBatisConfig {
}DataSourceProxyConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45package com.kayleh.springcloud.alibaba.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
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.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
public class DataSourceProxyConfig {
private String mapperLocations;
public DataSource druidDataSource(){
return new DruidDataSource();
}
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
11.主启动
1 | package com.kayleh.springcloud.alibaba; |
新建库存Storage-Module
1.seata-order-service2002
2.POM
1 | <dependencies> |
3.YML
1 | server: |
4.file.conf
1 | transport { |
5.registry.conf
1 | registry { |
6.domain
CommonResult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.kayleh.springcloud.alibaba.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
public class CommonResult<T>
{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}Storage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.kayleh.springcloud.alibaba.domain;
import lombok.Data;
public class Storage {
private Long id;
// 产品id
private Long productId;
//总库存
private Integer total;
//已用库存
private Integer used;
//剩余库存
private Integer residue;
}
7.Dao接口及实现
StorageDao
1
2
3
4
5
6
7
8
9
10package com.kayleh.springcloud.alibaba.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
public interface StorageDao {
//扣减库存信息
void decrease(; Long productId, Integer count)
}resources文件夹下新建mapper文件夹后添加
StorageMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<mapper namespace="com.kayleh.springcloud.alibaba.dao.StorageDao">
<resultMap id="BaseResultMap" type="com.kayleh.springcloud.alibaba.domain.Storage">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="total" property="total" jdbcType="INTEGER"/>
<result column="used" property="used" jdbcType="INTEGER"/>
<result column="residue" property="residue" jdbcType="INTEGER"/>
</resultMap>
<update id="decrease">
UPDATE
t_storage
SET
used = used + #{count},residue = residue - #{count}
WHERE
product_id = #{productId}
</update>
</mapper>
8.Service接口及实现
StorageService
1
2
3
4
5
6
7
8package com.kayleh.springcloud.alibaba.service;
public interface StorageService {
// 扣减库存
void decrease(Long productId, Integer count);
}StorageServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package com.kayleh.springcloud.alibaba.service.impl;
import com.kayleh.springcloud.alibaba.dao.StorageDao;
import com.kayleh.springcloud.alibaba.service.StorageService ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
public class StorageServiceImpl implements StorageService {
private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
private StorageDao storageDao;
// 扣减库存
public void decrease(Long productId, Integer count) {
LOGGER.info("------->storage-service中扣减库存开始");
storageDao.decrease(productId,count);
LOGGER.info("------->storage-service中扣减库存结束");
}
}
9.Controller
1 | package com.kayleh.springcloud.alibaba.controller; |
10.Config配置
MyBatisConfig
1
2
3
4
5
6
7
8
9
10package com.kayleh.springcloud.alibaba.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
public class MyBatisConfig {
}DataSourceProxyConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43package com.kayleh.springcloud.alibaba.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
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.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
public class DataSourceProxyConfig {
private String mapperLocations;
public DataSource druidDataSource(){
return new DruidDataSource();
}
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
11.主启动
1 | package com.kayleh.springcloud.alibaba; |
新建账户Account-Module
1.seata-account-service2003
2.POM
1 | <dependencies> |
3.YML
1 | server: |
4.file.conf
1 | transport { |
5.registry.conf
1 | registry { |
6.domain
CommonResult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.kayleh.springcloud.alibaba.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
public class CommonResult<T>
{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}Account
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package com.kayleh.springcloud.alibaba.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
public class Account {
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* 总额度
*/
private BigDecimal total;
/**
* 已用额度
*/
private BigDecimal used;
/**
* 剩余额度
*/
private BigDecimal residue;
}
7.Dao接口及实现
AccountDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.kayleh.springcloud.alibaba.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
public interface AccountDao {
/**
* 扣减账户余额
*/
void decrease(; Long userId, BigDecimal money)
}resources文件夹下新建mapper文件夹后添加
AccountMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<mapper namespace="com.kayleh.springcloud.alibaba.dao.AccountDao">
<resultMap id="BaseResultMap" type="com.kayleh.springcloud.alibaba.domain.Account">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="user_id" property="userId" jdbcType="BIGINT"/>
<result column="total" property="total" jdbcType="DECIMAL"/>
<result column="used" property="used" jdbcType="DECIMAL"/>
<result column="residue" property="residue" jdbcType="DECIMAL"/>
</resultMap>
<update id="decrease">
UPDATE t_account
SET
residue = residue - #{money},used = used + #{money}
WHERE
user_id = #{userId};
</update>
</mapper>
8.Service接口及实现
AccountService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.kayleh.springcloud.alibaba.service;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.math.BigDecimal;
public interface AccountService {
/**
* 扣减账户余额
*/
void decrease(; Long userId, BigDecimal money)
}AccountServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38package com.kayleh.springcloud.alibaba.service.impl;
import com.kayleh.springcloud.alibaba.dao.AccountDao;
import com.kayleh.springcloud.alibaba.service.AccountService ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
/**
* 账户业务实现类
*/
public class AccountServiceImpl implements AccountService {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
AccountDao accountDao;
/**
* 扣减账户余额
*/
public void decrease(Long userId, BigDecimal money) {
LOGGER.info("------->account-service中扣减账户余额开始");
try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
accountDao.decrease(userId,money);
LOGGER.info("------->account-service中扣减账户余额结束");
}
}
9.Controller
1 | package com.kayleh.springcloud.alibaba.controller; |
10.Config配置
MyBatisConfig
1
2
3
4
5
6
7
8
9
10package com.kayleh.springcloud.alibaba.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
public class MyBatisConfig {
}DataSourceProxyConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43package com.kayleh.springcloud.alibaba.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
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.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
public class DataSourceProxyConfig {
private String mapperLocations;
public DataSource druidDataSource(){
return new DruidDataSource();
}
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
11.主启动
1 | package com.kayleh.springcloud.alibaba; |
Test
下订单->减库存->扣余额->改(订单)状态
数据库初始情况
正常下单
http://localhost:2001/order/create?userid=1&producrid=1&counr=10&money=100
数据库情况
超时异常,没加@GlobalTransactional
seata-order-service2003的decrease
1 | 在accountDao调用decrease之前 |
AccountServiceImpl添加超时
数据库情况
故障情况
当库存和账户余额扣减后,订单状态并没有设置为已经完成,没有从零改为1
而且由于feign的重试机制,账户余额还有可能被多次扣减
超时异常,添加@GlobalTransactional
orderServiceImpl
1 |
AccountServiceImpl添加超时
OrderServiceImpl@GlobalTransactional
下单后数据库数据并没有任何改变
记录都添加不进来
Seata之原理简介
Seata
2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案
Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架
2020起初,参加工作后用1.0以后的版本
再看TC/TM/RM三大组件
分布式事务的执行流程
TM开启分布式事务(TM向TC注册全局事务记录)
换业务场景,编排数据库,服务等事务内资源(RM向TC汇报资源准备状态)
TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务)
TC汇总事务信息,决定分布式事务是提交还是回滚
TC通知所有RM提交/回滚资源,事务二阶段结束。
AT模式如何做到对业务的无侵入
是什么
一阶段加载
二阶段提交
二阶段回滚
debug
补充
