• Seata-TCC模式


    1、TCC模式介绍

            Seata 开源了 TCC 模式,该模式由蚂蚁金服贡献。TCC 模式需要用户根据自己的业务场景实现Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段 执行 Try 方式,在二阶段提交执行Confirm方法,二阶段回滚执行 Cancel 方法。

    TCC 三个方法描述: 

    • Try:资源的检测和预留;
    • Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
    • Cancel:预留资源释放。

    业务模型分 2 阶段设计:

      用户接入 TCC ,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。

            以“扣钱”场景为例,在接入 TCC 前,对 A 账户的扣钱,只需一条更新账户余额的 SQL 便能完成;但是在接入 TCC 之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆成两阶段,实现成三个方法,并且保证一阶段 Try 成功的话二阶段 Confirm 一定能成功。

            Try 方法作为一阶段准备方法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是检查账户余额是否充足,预留转账资金,预留的方式就是冻结 A 账户的 转账资金。Try 方法执行之后,账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使用。 

            二阶段 Confirm 方法执行真正的扣钱操作。Confirm 会使用 Try 阶段冻结的资金,执行账号扣款。Confirm 方法执行之后,账号 A 在一阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。如果二阶段是回滚的话,就需要在 Cancel 方法内释放一阶段 Try 冻结的 30 元,使账号 A 的回到初始状态,100 元全部可用。

            用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。

    2、TCC模式改造案例

    2.1、RM端改造

            针对RM端,实现起来需要完成try/commit/rollback的实现,所以步骤相对较多但是前三步骤和AT模式一样

    (1) 修改数据库表结构,增加预留检查字段,用于提交和回滚

    1. ALTER TABLE `seata_order`.`t_order` ADD COLUMN `status` INT ( 0 ) NULL COMMENT '订单状态-0不可⽤,事务未提交 , 1-可⽤,事务提交';
    2. ALTER TABLE `seata_points`.`t_points` ADD COLUMN `frozen_points` INT ( 0 ) NULL DEFAULT 0 COMMENT '冻结积分' AFTER `points`;
    3. ALTER TABLE `seata_storage`.`t_storage` ADD COLUMN `frozen_storage` INT ( 0 ) NULL DEFAULT 0 COMMENT '冻结库存' AFTER `goods_id`;

    (2)lagou_order工程改造

            接口

    1. package com.lagou.order.service;
    2. import com.baomidou.mybatisplus.extension.service.IService;
    3. import com.lagou.order.entity.Order;
    4. import io.seata.rm.tcc.api.BusinessActionContext;
    5. import io.seata.rm.tcc.api.BusinessActionContextParameter;
    6. import io.seata.rm.tcc.api.LocalTCC;
    7. import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
    8. /**
    9. * 接口被Seata管理,根据事务的状态完成提交或回滚操作
    10. */
    11. @LocalTCC
    12. public interface OrderService extends IService {
    13. @TwoPhaseBusinessAction(name = "addTCC", commitMethod = "addCommit", rollbackMethod = "addRollback")
    14. // 该注解中name属性定义的名称必须保持全局唯一, commitMethod默认名称为"commit",rollbackMethod默认名称为"rollback"
    15. void add(@BusinessActionContextParameter(paramName = "order") Order order); // 该注解是将此方法中的Order参数放到BusinessActionContext上下文对象中,供我们定义的方法使用,paramName默认为 ""
    16. public boolean addCommit(BusinessActionContext context); // 该方法的返回值类型是固定的
    17. public boolean addRollback(BusinessActionContext context); // 该方法的返回值类型是固定的
    18. }

            实体类增加一个字段

    1. package com.lagou.order.entity;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableField;
    4. import com.baomidou.mybatisplus.annotation.TableId;
    5. import com.baomidou.mybatisplus.annotation.TableName;
    6. import lombok.Data;
    7. import java.io.Serializable;
    8. /**
    9. * 订单实体类
    10. */
    11. @Data
    12. @TableName("t_order")
    13. public class Order implements Serializable {
    14. @TableId
    15. private Long id;//订单id
    16. @TableField
    17. private Integer goodsId;// 商品ID
    18. @TableField
    19. private Integer num;//商品数量
    20. @TableField
    21. private Double money;//商品总金额
    22. @TableField
    23. private java.util.Date createTime;//订单创建时间
    24. @TableField
    25. private String username;//用户名称
    26. @TableField
    27. private Integer status; // 订单状态
    28. }

            实现类

    1. package com.lagou.order.service.impl;
    2. import com.alibaba.fastjson.JSON;
    3. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    4. import com.lagou.order.entity.Order;
    5. import com.lagou.order.mapper.OrderMapper;
    6. import com.lagou.order.service.OrderService;
    7. import io.seata.rm.tcc.api.BusinessActionContext;
    8. import lombok.extern.slf4j.Slf4j;
    9. import org.springframework.stereotype.Service;
    10. import java.util.Date;
    11. @Slf4j
    12. @Service
    13. public class OrderServiceImpl extends ServiceImpl implements OrderService {
    14. @Override
    15. public void add(Order order) {
    16. order.setCreateTime(new Date());//设置订单创建时间
    17. order.setStatus(0); // try 阶段-预检查
    18. this.save(order);//保存订单
    19. }
    20. @Override
    21. public boolean addCommit(BusinessActionContext context) {
    22. Object jsonOrder = context.getActionContext("order");
    23. Order order = JSON.parseObject(jsonOrder.toString(), Order.class);
    24. order = this.getById(order.getId());
    25. if (order != null) {
    26. order.setStatus(1); // 提交操作,1代表订单可用
    27. this.saveOrUpdate(order);
    28. }
    29. log.info("----------->xid"+context.getXid()+" 提交成功!");
    30. return true; // 注意方法必须返回为true
    31. }
    32. @Override
    33. public boolean addRollback(BusinessActionContext context) {
    34. Object jsonOrder = context.getActionContext("order");
    35. Order order = JSON.parseObject(jsonOrder.toString(), Order.class);
    36. order = this.getById(order.getId());
    37. if (order != null) {
    38. this.removeById(order.getId()); // 回滚操作-删除订单
    39. }
    40. log.info("----------->xid"+context.getXid()+" 回滚成功!");
    41. return true;
    42. }
    43. }

    (3)lagou_points工程改造

            接口改造

    1. package com.lagou.points.service;
    2. import com.baomidou.mybatisplus.extension.service.IService;
    3. import com.lagou.points.entity.Points;
    4. import io.seata.rm.tcc.api.BusinessActionContext;
    5. import io.seata.rm.tcc.api.BusinessActionContextParameter;
    6. import io.seata.rm.tcc.api.LocalTCC;
    7. import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
    8. @LocalTCC
    9. public interface PointsService extends IService {
    10. @TwoPhaseBusinessAction(name = "increaseTCC", commitMethod = "increaseCommit", rollbackMethod = "increaseRollback")
    11. public void increase(@BusinessActionContextParameter(paramName = "username") String username,
    12. @BusinessActionContextParameter(paramName = "points") Integer points);
    13. public boolean increaseCommit(BusinessActionContext context);
    14. public boolean increaseRollback(BusinessActionContext context);
    15. }

             实体类增加一个字段

    1. package com.lagou.points.entity;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableField;
    4. import com.baomidou.mybatisplus.annotation.TableId;
    5. import com.baomidou.mybatisplus.annotation.TableName;
    6. import lombok.Data;
    7. /**
    8. * 积分实体类
    9. */
    10. @Data
    11. @TableName("t_points")
    12. public class Points {
    13. @TableId(value = "ID", type = IdType.AUTO)
    14. private Integer id;//积分ID
    15. @TableField
    16. private String username;//用户名
    17. @TableField
    18. private Integer points;//增加的积分
    19. @TableField
    20. private Integer frozenPoints; // 冻结积分
    21. }

            实现类改造

    1. package com.lagou.points.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    4. import com.lagou.points.mapper.PointsMapper;
    5. import com.lagou.points.entity.Points;
    6. import com.lagou.points.service.PointsService;
    7. import io.seata.rm.tcc.api.BusinessActionContext;
    8. import lombok.extern.slf4j.Slf4j;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.stereotype.Service;
    11. /**
    12. * 会员积分服务
    13. */
    14. @Slf4j
    15. @Service
    16. public class PointsServiceImpl extends ServiceImpl implements PointsService {
    17. @Autowired
    18. PointsMapper pointsMapper;
    19. /**
    20. * 会员增加积分
    21. *
    22. * @param username 用户名
    23. * @param points 增加的积分
    24. * @return 积分对象
    25. */
    26. public void increase(String username, Integer points) {
    27. QueryWrapper wrapper = new QueryWrapper();
    28. wrapper.lambda().eq(Points::getUsername, username);
    29. Points userPoints = this.getOne(wrapper);
    30. if (userPoints == null) {
    31. userPoints = new Points();
    32. userPoints.setUsername(username);
    33. // userPoints.setPoints(points);
    34. userPoints.setFrozenPoints(points); // try-设置冻结积分
    35. } else {
    36. // userPoints.setPoints(userPoints.getPoints() + points);
    37. userPoints.setFrozenPoints(points); // try-设置冻结积分
    38. }
    39. this.saveOrUpdate(userPoints);
    40. }
    41. @Override
    42. public boolean increaseCommit(BusinessActionContext context) {
    43. // 查询⽤户积分
    44. QueryWrapper wrapper = new QueryWrapper();
    45. wrapper.lambda().eq(Points::getUsername, context.getActionContext("username"));
    46. Points userPoints = this.getOne(wrapper);
    47. if (userPoints != null) {
    48. // 增加用户积分
    49. userPoints.setPoints(userPoints.getPoints() + userPoints.getFrozenPoints());
    50. // 冻结积分清零
    51. userPoints.setFrozenPoints(0);
    52. this.saveOrUpdate(userPoints);
    53. }
    54. log.info("--------->xid=" + context.getXid() + " 提交成功!");
    55. return true;
    56. }
    57. @Override
    58. public boolean increaseRollback(BusinessActionContext context) {
    59. // 查询用户积分
    60. QueryWrapper wrapper = new QueryWrapper();
    61. wrapper.lambda().eq(Points::getUsername, context.getActionContext("username"));
    62. Points userPoints = this.getOne(wrapper);
    63. if (userPoints != null) {
    64. // 冻结积分清零
    65. userPoints.setFrozenPoints(0);
    66. this.saveOrUpdate(userPoints);
    67. }
    68. log.info("--------->xid=" + context.getXid() + " 回滚成功!");
    69. return true;
    70. }
    71. }

    (4)lagou_stroage工程改造

            接口改造

    1. package com.lagou.storage.service;
    2. import com.baomidou.mybatisplus.extension.service.IService;
    3. import com.lagou.storage.entity.Storage;
    4. import io.seata.rm.tcc.api.BusinessActionContext;
    5. import io.seata.rm.tcc.api.BusinessActionContextParameter;
    6. import io.seata.rm.tcc.api.LocalTCC;
    7. import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
    8. /**
    9. * 仓库服务
    10. */
    11. @LocalTCC
    12. public interface StorageService extends IService {
    13. @TwoPhaseBusinessAction(name = "decreaseTCC", commitMethod = "decreaseCommit", rollbackMethod = "decreaseRollback")
    14. public void decrease(@BusinessActionContextParameter(paramName = "goodsId") Integer goodsId,
    15. @BusinessActionContextParameter(paramName = "quantity") Integer quantity);
    16. public boolean decreaseCommit(BusinessActionContext context);
    17. public boolean decreaseRollback(BusinessActionContext context);
    18. }

            实体类新增一个字段

    1. package com.lagou.storage.entity;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableField;
    4. import com.baomidou.mybatisplus.annotation.TableId;
    5. import com.baomidou.mybatisplus.annotation.TableName;
    6. import lombok.Data;
    7. @Data
    8. @TableName("t_storage")
    9. public class Storage {
    10. @TableId(value = "id", type = IdType.AUTO)
    11. private Integer id;// 库存ID
    12. @TableField
    13. private String goodsId;// 商品ID
    14. @TableField
    15. private Integer storage;// 库存量
    16. @TableField
    17. private Integer frozenStorage;// 冻结库存
    18. }

            实现类改造

    1. package com.lagou.storage.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    3. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    4. import com.lagou.storage.entity.Storage;
    5. import com.lagou.storage.mapper.StorageMapper;
    6. import com.lagou.storage.service.StorageService;
    7. import io.seata.rm.tcc.api.BusinessActionContext;
    8. import lombok.extern.slf4j.Slf4j;
    9. import org.springframework.stereotype.Service;
    10. /**
    11. * 仓库服务
    12. */
    13. @Service
    14. @Slf4j
    15. public class StorageServiceImpl extends ServiceImpl implements StorageService {
    16. /**
    17. * 减少库存
    18. *
    19. * @param goodsId 商品ID
    20. * @param quantity 减少数量
    21. * @return 库存对象
    22. */
    23. public void decrease(Integer goodsId, Integer quantity) {
    24. QueryWrapper wrapper = new QueryWrapper();
    25. wrapper.lambda().eq(Storage::getGoodsId, goodsId);
    26. Storage goodsStorage = this.getOne(wrapper);
    27. if (goodsStorage.getStorage() >= quantity) {
    28. // goodsStorage.setStorage(goodsStorage.getStorage() - quantity);
    29. // 设置冻结库存
    30. goodsStorage.setFrozenStorage(quantity);
    31. } else {
    32. throw new RuntimeException(goodsId + "库存不足,目前剩余库存:" + goodsStorage.getStorage());
    33. }
    34. this.saveOrUpdate(goodsStorage);
    35. }
    36. @Override
    37. public boolean decreaseCommit(BusinessActionContext context) {
    38. QueryWrapper wrapper = new QueryWrapper();
    39. wrapper.lambda().eq(Storage::getGoodsId,
    40. context.getActionContext("goodsId"));
    41. Storage goodsStorage = this.getOne(wrapper);
    42. if (goodsStorage != null) {
    43. // 扣减库存
    44. goodsStorage.setStorage(goodsStorage.getStorage() - goodsStorage.getFrozenStorage());
    45. // 冻结库存清零
    46. goodsStorage.setFrozenStorage(0);
    47. this.saveOrUpdate(goodsStorage);
    48. }
    49. log.info("--------->xid=" + context.getXid() + " 提交成功!");
    50. return true;
    51. }
    52. @Override
    53. public boolean decreaseRollback(BusinessActionContext context) {
    54. QueryWrapper wrapper = new QueryWrapper();
    55. wrapper.lambda().eq(Storage::getGoodsId, context.getActionContext("goodsId"));
    56. Storage goodsStorage = this.getOne(wrapper);
    57. if (goodsStorage != null) {
    58. // 冻结库存清零
    59. goodsStorage.setFrozenStorage(0);
    60. this.saveOrUpdate(goodsStorage);
    61. }
    62. log.info("--------->xid=" + context.getXid() + " 回滚成功!");
    63. return true;
    64. }
    65. }

    2.2、TM端改造

            针对我们工程lagou_bussiness是事务的发起者,所以是TM端,其它工程为RM端. 所以我们只需要在lagou_common_db完成即可,因为lagou_bussiness方法里面没有对数据库操作.所以只需要将之前AT模式的代理数据源去掉即可.注意:如果lagou_bussiness也对数据库操作了.也需要完成try/commit/rollback的实现 。

    代码实现:

    1. package com.lagou.bussiness.service.impl;
    2. import com.lagou.bussiness.feign.OrderServiceFeign;
    3. import com.lagou.bussiness.feign.PointsServiceFeign;
    4. import com.lagou.bussiness.feign.StorageServiceFeign;
    5. import com.lagou.bussiness.service.BussinessService;
    6. import com.lagou.bussiness.utils.IdWorker;
    7. import io.seata.spring.annotation.GlobalTransactional;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.stereotype.Service;
    10. import org.springframework.transaction.annotation.Transactional;
    11. /**
    12. * 业务逻辑
    13. */
    14. @Service
    15. public class BussinessServiceImpl implements BussinessService {
    16. @Autowired
    17. OrderServiceFeign orderServiceFeign;
    18. @Autowired
    19. PointsServiceFeign pointsServiceFeign;
    20. @Autowired
    21. StorageServiceFeign storageServiceFeign;
    22. @Autowired
    23. IdWorker idWorker;
    24. /**
    25. * 商品销售
    26. *
    27. * @param goodsId 商品id
    28. * @param num 销售数量
    29. * @param username 用户名
    30. * @param money 金额
    31. */
    32. // @Transactional
    33. @GlobalTransactional(name = "sale", timeoutMills = 100000, rollbackFor = Exception.class)
    34. public void sale(Integer goodsId, Integer num, Double money, String username) {
    35. //创建订单
    36. orderServiceFeign.addOrder(idWorker.nextId(), goodsId, num, money, username);
    37. //增加积分
    38. pointsServiceFeign.increase(username, (int) (money / 10));
    39. //扣减库存
    40. storageServiceFeign.decrease(goodsId, num);
    41. }
    42. }

    示例代码下载 

  • 相关阅读:
    中华人民共和国消费者权益保护法
    智慧矿山解决方案-最新全套文件
    金秋开学季,先进计算基础训练营开营了|猿代码科技
    计算机图形学入门28:相机、透镜和光场
    QT中的样式表.qss文件
    0基础学Java(30)
    Java(solon) -VS- Go(gin) 之内存与并发测试
    基站天线效率相关技术研究
    【Linux Clock Framework】
    【TES720D-KIT】青翼自研基于复旦微FMQL20S400全国产化ARM开发套件(核心板+底板)
  • 原文地址:https://blog.csdn.net/weixin_52851967/article/details/126703159