什么是分布式事务问题?

单体应用

  单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。

微服务应用

  随着业务需求的变化,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

小结

  在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,一次业务操作需要操作多个数据源或需要进行远程调用,就会产生分布式事务问题。

Seata 是什么?

  Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

  官网:http://seata.io/

Seata 组成

Transaction ID(XID)

  全局唯一的事务id

三组件

  Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚

  Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议

  Resource Manager(RM):控制分支事务,负责分支注册、状态汇报,并接受事务协调的指令,驱动分支(本地)事务的提交和回滚

Seata 分布式事务处理过程

  过程图:

  

  说明:

  1、TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;

  2、XID 在微服务调用链路的上下文中传播;

  3、RM 向 TC 注册分支事务,将其纳入 XID 对应的全局事务的管辖;

  4、TM 向 TC 发起针对 XID 的全局提交或回滚决议;

  5、TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

搭建demo

  • cloud-seata-order9011

    application.yml

# 端口
server:
  port: 9011
spring:
  application:
    name: seata-order-service
  #   数据源基本配置
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alibaba:
      seata:
        tx-service-group: my_test_tx_group
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata-order?allowPublicKeyRetrieval=true&useSSL=true
    username: root
    password: 678678
    #druid
    type: com.alibaba.druid.pool.DruidDataSource
mybatis:
  mapperLocations: classpath:mapper/*Mapper.xml
# seata
seata:
  enabled: true
  application-id: applicationName
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: true
  config:
    type: nacos
    nacos:
      namespace:
      serverAddr: 127.0.0.1:8848
      group: SEATA_GROUP
      userName: "nacos"
      password: "nacos"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace:
      userName: "nacos"
      password: "nacos"
  service:
    vgroup-mapping: my_test_tx_group

    

 

     OrderServiceImpl

 1 package com.sdkj.service.impl;
 2 
 3 import com.sdkj.dao.OrderDao;
 4 import com.sdkj.entity.Order;
 5 import com.sdkj.service.AccountService;
 6 import com.sdkj.service.OrderService;
 7 import com.sdkj.service.StorageService;
 8 import io.seata.spring.annotation.GlobalTransactional;
 9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.stereotype.Service;
11 
12 /**
13  * @Author wangshuo
14  * @Date 2022/5/30, 22:06
15  * Please add a comment
16  */
17 @Service
18 public class OrderServiceImpl implements OrderService {
19 
20     @Autowired
21     private OrderDao orderDao;
22     @Autowired
23     private StorageService storageService;
24     @Autowired
25     private AccountService accountService;
26 
27     //全局事务控制
28     @GlobalTransactional(name = "my_test_tx_group",rollbackFor = Exception.class)
29     @Override
30     public void create(Order order) {
31         System.out.println("create start");
32         //减库存
33         storageService.decrease(order.getProductId(),order.getCount());
34         System.out.println("storage decrease success");
35         //扣减余额
36         accountService.decrease(order.getUserId(), order.getMoney());
37         System.out.println("order decrease success");
38         //修改订单状态
39         orderDao.update(order.getId(), order.getUserId(), 0);
40         //end
41         System.out.println("end");
42     }
43 }
  • cloud-seata-storage9012 / cloud-seata-account9013

    application.yml

# 端口
server:
  port: 9012
spring:
  application:
    name: seata-storage-service
  #   数据源基本配置
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alibaba:
      seata:
        tx-service-group: my_test_tx_group
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata-storage?allowPublicKeyRetrieval=true&useSSL=true
    username: root
    password: 678678
feign.hystrix.enabled: true
hystrix:
  command:
    default:
      circuitBreaker:
        sleepWindowInMilliseconds: 30000
        requestVolumeThreshold: 10
      execution:
        isolation:
          strategy: SEMAPHORE
          thread:
            timeoutInMilliseconds: 100000
# seata
seata:
  enabled: true
  application-id: applicationName
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: true
  config:
    type: nacos
    nacos:
      namespace:
      serverAddr: 127.0.0.1:8848
      group: SEATA_GROUP
      userName: "nacos"
      password: "nacos"
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      namespace:
      userName: "nacos"
      password: "nacos"
  service:
    vgroup-mapping: my_test_tx_group

    

 

     StorageService

 1 package com.sdkj.service;
 2 
 3 /**
 4  * @Author wangshuo
 5  * @Date 2022/5/31, 9:31
 6  * AT模式
 7  */
 8 public interface StorageService {
 9 
10     Integer decrease(Long productId, Integer count);
11 }

    StorageServiceImpl

 1 package com.sdkj.service.impl;
 2 
 3 import com.sdkj.dao.StorageMapper;
 4 import com.sdkj.service.StorageService;
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.stereotype.Service;
 7 
 8 /**
 9  * @Author wangshuo
10  * @Date 2022/5/31, 9:42
11  * Please add a comment
12  */
13 @Service
14 public class StorageServiceImpl implements StorageService {
15 
16     @Autowired
17     private StorageMapper storageMapper;
18 
19     @Override
20     public Integer decrease(Long productId, Integer count) {
21         return storageMapper.decrease(productId, count);
22     }
23 }

    TCCStorageService

 1 package com.sdkj.service;
 2 
 3 import io.seata.rm.tcc.api.BusinessActionContext;
 4 import io.seata.rm.tcc.api.BusinessActionContextParameter;
 5 import io.seata.rm.tcc.api.LocalTCC;
 6 import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
 7 
 8 /**
 9  * @Author wangshuo
10  * @Date 2022/6/1, 17:34
11  * TCC模式
12  * 接口被seata管理 根据事务的状态完成提交或回滚的操作
13  */
14 @LocalTCC
15 public interface TCCStorageService {
16 
17     @TwoPhaseBusinessAction(name = "StorageTcc",commitMethod = "addCommit",rollbackMethod = "addRollBack")
18     Integer decrease(@BusinessActionContextParameter(paramName = "productId") Long productId,
19                      @BusinessActionContextParameter(paramName = "count") Integer count);
20 
21     public boolean addCommit(BusinessActionContext context);
22 
23     public boolean addRollBack(BusinessActionContext context);
24 }

    TCCStorageServiceImpl

 1 package com.sdkj.service.impl;
 2 
 3 import com.alibaba.fastjson.JSON;
 4 import com.sdkj.service.TCCStorageService;
 5 import io.seata.rm.tcc.api.BusinessActionContext;
 6 import org.springframework.stereotype.Service;
 7 
 8 /**
 9  * @Author wangshuo
10  * @Date 2022/6/1, 17:40
11  * Please add a comment
12  */
13 @Service
14 public class TCCStorageServiceImpl implements TCCStorageService {
15 
16     //try阶段
17     @Override
18     public Integer decrease(Long productId, Integer count) {
19         /*
20             新增
21                 到数据库一条状态为尝试的数据
22             修改
23                 数据库设计时多加一个对应的冻结字段,try阶段修改先修改try对应字段
24          */
25         return null;
26     }
27 
28     //commit阶段
29     @Override
30     public boolean addCommit(BusinessActionContext context) {
31         //获取参数对象(Json格式)
32         Object productId = context.getActionContext("productId");
33         Long pid = JSON.parseObject(productId.toString(), Long.class);//也可以直接转化为实体类中的数据
34 
35         //提交到数据库
36         return false;
37     }
38 
39     //
40     @Override
41     public boolean addRollBack(BusinessActionContext context) {
42         //获取参数对象(Json格式)
43         Object productId = context.getActionContext("productId");
44         Long pid = JSON.parseObject(productId.toString(), Long.class);//也可以直接转化为实体类中的数据
45 
46         /*
47             新增
48                 删除对应数据
49             修改
50                 将冻结字段置为0或初始值
51          */
52         return false;
53     }
54 }

 

AT / TCC优缺点对比

  • AT优点:

    使用简单

    没有入侵性

  • AT缺点:

    性能较差(锁)

  • TCC优点:

    性能较好

  • TCC缺点:

    编码 / 数据库表结构更加复杂

    有一定入侵性