事务

ACID特性

原子性、一致性、隔离性、永久性

单体架构中,学习过事务,但是只能保证一个项目的数据一致性,在分布式中如何保证?需要用到一个工具:seata

Seata

什么是Seata?

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务,也是Spring Cloud Alibaba提供的组件

Seata保证微服务远程调用业务的原子性

Seata将为用户提供了ATTCCSAGAXA事务模式,为用户打造一站式的分布式解决方案

Seata的运行原理(AT模式)

image
Seata构成部分包含:

  • TC:事务协调器

  • TM:事务管理器(事务的起点)

  • RM:资源管理器

我们项目使用的AT模式(默认)来完成分布式事务的解决方案

AT模式的运行过程

  1. 事务的发起方(TM)会事务协调器(TC)申请一个全局事务id,并保存
  2. Seata会管理事务中所有相关的参与方的数据源,将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata组件规定的表,没有它就不能实现效果,依靠它来实现提交或回滚的操作
  3. 事务的发起方会联通全局id一起通过远程调用运行资源管理器(RM)中的方法
  4. RM接收到全局id,去运行指定方法,并将运行结果的状态发送给TC
  5. 如果所有分支运行都正常,事务管理器会通过事务协调器通知所有模块执行数据库操作,真正影响数据库内容;如果有任何一个分支模块运行异常,都会通知TC,再由TC通知所有分支将数据库回滚,恢复成运行之前的样子

AT模式运行有一个非常明显的前提条件,这个条件不满足就无法使用AT模式

这个条件就是事务所有分支必须是操作关系型数据库

但是如果我们在业务过程中有一个节点操作的是redis或者其他非关系型数据库,就无法使用AT模式

TCC模式

简单来说,TCC模式 就是自己编写代码来完成事务的提交或回滚

TCC模式要求我们再每个参与方事务的业务中编写一组方法:preparecommitrollback

prepare :无论事务成功还是失败都会运行的代码

commit :当整体事务运行成功时运行的方法

rollback :当整体事务失败时运行的方法

优点:虽然代码是自己编写的,但是事务整体提交或回滚的机制仍然可用(还是由TC来调度)

缺点:每个业务都要编写3个方法来对应,代码冗余,业务入侵量大

SAGA模式

SAGA模式的思想是对应每个业务逻辑层编写一个新的类,可以设置指定的业务逻辑层方法发生异常时,运行当前编写的新类中的代码
这样编写不影响已经编写好的代码

一般用于修改已经完成的老代码

缺点是每个事务分支都要编写一个类来回滚业务,会造成类的数量较多,开发量比较大

XA模式

支持XA协议的数据库分布式事务,使用比较少

使用Seata

从百度网盘中下载Seata压缩包

解压到没有中文或空格的路径中即可,进入bin目录中有两个文件:seata-server.batseata-server.sh

打开dos窗口,输入以下命令

D:\software\seata\seata\seata-server-1.4.2\bin>seata-server.bat -h 127.0.0.1 -m file

image

在windows系统中运行seata可能出现不稳定的情况,重启seata即可解决

配置Seata

cart/stock/order 都是具备数据操作功能的模块,都需要添加seata依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--添加seata依赖-->
       <dependency>
           <groupId>io.seata</groupId>
           <artifactId>seata-spring-boot-starter</artifactId>
       </dependency>
       <!--Seata完成分布式事务需要的两个相关依赖(Seata需要下面两个依赖中的资源)-->
       <dependency>
           <groupId>com.github.pagehelper</groupId>
           <artifactId>pagehelper-spring-boot-starter</artifactId>
       </dependency>
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
       </dependency>

cart/stock/order 模块的application-dev.yml文件配置

1
2
3
4
5
6
7
seata:
tx-service-group: csmall_group  #定义分组名称,一般是为了区分项目
service:
  vgroup-mapping:
    csmall_group: default #csmall_group组使用默认的Seata配置完成事务
  grouplist:
    default: localhost:8091 #设置Seata所在的地址,默认端口是8091

business模块的配置更加简单,因为不需要操作数据库,所以只需要Seata的依赖

1
2
3
4
5
<!--添加seata依赖-->
       <dependency>
           <groupId>io.seata</groupId>
           <artifactId>seata-spring-boot-starter</artifactId>
       </dependency>

application-dev.yml

1
2
3
4
5
6
7
seata:
tx-service-group: csmall_group  #定义分组名称,一般是为了区分项目
service:
  vgroup-mapping:
    csmall_group: default #csmall_group组使用默认的Seata配置完成事务
  grouplist:
    default: localhost:8091 #设置Seata所在的地址,默认端口是8091s

添加完必要配置之后,如果要启动Seata非常简单,只要在TM的业务逻辑层方法上添加专用的注解即可

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
@Service
@Slf4j //提供log对象,可以用于打印日志
public class BusinessServiceImpl implements IBusinessService {

   //需要利用dubbo调用order模块中的新增订单方法
   @DubboReference
   private IOrderService dubboOrderService;

   //一旦编写@GlobalTransactional,标记这个方法就是seata分布式事务的起点了,也就是TM
   //最终的效果就是由当前方法引发的所有远程调用对数据的操作,要么都成功,要么都失败
   @GlobalTransactional
   @Override
   public void buy() {
       //模拟购买业务
       //创建OrderAddDTO类,并赋值
       OrderAddDTO orderAddDTO = new OrderAddDTO();
       orderAddDTO.setUserId("UU100");
       orderAddDTO.setCommodityCode("PC100");
       orderAddDTO.setCount(5);
       orderAddDTO.setMoney(100);
       //调用order模块的方法
       dubboOrderService.orderAdd(orderAddDTO);
       //目前现在控制台输出
       log.info("新增订单的信息为:{}",orderAddDTO);
  }
}

启动nacos和seata以及cart、stock、order、business模块进行测试,为了观察效果,添加异常代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@GlobalTransactional
   @Override
   public void buy() {
       //模拟购买业务
       //创建OrderAddDTO类,并赋值
       OrderAddDTO orderAddDTO = new OrderAddDTO();
       orderAddDTO.setUserId("UU100");
       orderAddDTO.setCommodityCode("PC100");
       orderAddDTO.setCount(5);
       orderAddDTO.setMoney(100);
       //调用order模块的方法
       dubboOrderService.orderAdd(orderAddDTO);
       //目前现在控制台输出
       log.info("新增订单的信息为:{}",orderAddDTO);
       //为了验证seata实现的分布式事务效果
       //我们在所有代码运行完毕之后,随机抛出异常,观察是否能回滚
       if (Math.random()<0.5){
           throw  new CoolSharkServiceException(ResponseCode.INTERNAL_SERVER_ERROR,
                   "发生随机异常,本次操作回滚");
      }
  }