什么是分布式事务问题?
单体应用
单体应用中,一个业务操作需要调用三个模块完成,此时数据的一致性由本地事务来保证。
微服务应用
随着业务需求的变化,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
小结
在微服务架构中由于全局数据一致性没法保证产生的问题就是分布式事务问题。简单来说,一次业务操作需要操作多个数据源或需要进行远程调用,就会产生分布式事务问题。
Seata 是什么?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
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缺点:
编码 / 数据库表结构更加复杂
有一定入侵性
本文来自博客园,作者:荣慕平,转载请注明原文链接:https://www.cnblogs.com/rongmuping/articles/16336077.html