• 分布式事务( TCC) seata eurake springboot mysql (1.4.2)


    项目搭建见:  分布式事务( XA) -- seata eurake springboot mysql (1.4.2)     https://www.cnblogs.com/lshan/p/16533280.html

    官网示例代码: https://github.com/seata/seata-samples

     my TCC 测试代码 :  https://gitee.com/lshan523/seata-demo

    适用场景:由于从业务服务是同步调用,其结果会影响到主业务服务的决策,因此通用型 TCC 分布式事务解决方案适用于执行时间确定且较短的业务,比如互联网金融企业最核心的三个服务:交易、支付、账务:

    案例场景:  

    TCC 模型的并发事务:

    案例:
    支付服务二阶段

    1. 先调用账务服务的 Confirm 接口,扣除买家冻结资金;增加卖家可用资金。

    2.调用成功后,支付服务修改支付订单为完成状态,完成支付。

    此处演示冻结操作:

    1. DO:

    @Data
    @Document("account_freeze_tbl")
    @NoArgsConstructor
    @AllArgsConstructor
    public class AccountFreeze {
        @Id private String xid;
            private String userId;
            private Integer freezeMoney;
            private Integer state;
            private Boolean active=true;
        public AccountFreeze(String xid, String userId, Integer freezeMoney, Integer state) {
            this.xid = xid;
            this.userId = userId;
            this.freezeMoney = freezeMoney;
            this.state = state;
        }
        public static abstract class State {
            public final static int TRY = 0;
            public final static int CONFIRM = 1;
            public final static int CANCEL = 2;
        }
    }
    
    
    @Data
    @Document("account_tbl")
    public class Account {
        @Id
        private String id;
        private String userId;
        private Integer money;
        private Boolean active=true;
    }

    2.  业务实现:   

    @TwoPhaseBusinessAction(name = "deduct",  name 保证唯一
    @LocalTCC
    public interface AccountTccService {
        /**
         * @param userId
         * @param money
         */
        @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
        void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
                    @BusinessActionContextParameter(paramName = "money") int money);
        boolean confirm(BusinessActionContext ctx);
        boolean cancel(BusinessActionContext ctx);
    }

    实现: 

    此处通过  xid 实现幂等操作

    @Service
    public class AccountTccServiceImpl implements AccountTccService {
        @Autowired
        private AccountFreezeService accountFreezeService;
        @Autowired
        private AccountService accountService;
        @Override
        public void deduct(String userId, int money) {
            String xid = RootContext.getXID();
            // 查询冻结记录,如果有,就是cancel执行过,不能继续执行
            AccountFreeze oldfreeze = accountFreezeService.findOne(MapUtils.of("xid",xid));
            if (oldfreeze != null){
                return;
            }
            // 扣除
            accountService.updateByKeyInc(MapUtils.of("userId",userId),"money",-money);
            // 记录
            AccountFreeze freeze = new AccountFreeze(xid,userId,money,AccountFreeze.State.TRY);
            accountFreezeService.saveOrUpdate(freeze);
        }
       
       // 确认提交后,删除冻结 @Override
    public boolean confirm(BusinessActionContext ctx) { String xid = ctx.getXid(); long l = accountFreezeService.delById(xid); return l == 1; } @Override public boolean cancel(BusinessActionContext ctx) { String xid = ctx.getXid(); // 查询冻结记录 AccountFreeze freeze = accountFreezeService.findOne(MapUtils.of("_id",xid)); if(null == freeze){ // try没有执行,需要空回滚 freeze = new AccountFreeze(xid,ctx.getActionContext("userId").toString(),0,AccountFreeze.State.CANCEL); accountFreezeService.saveOrUpdate(freeze); return true; } // 幂等判断 if(freeze.getState() == AccountFreeze.State.CANCEL){ return true; } // 恢复金额 // accountService.refund(freeze.getUserId(), freeze.getFreezeMoney()); accountService.updateByKeyInc(MapUtils.of("userId",freeze.getUserId()),"money",+freeze.getFreezeMoney()); long count = accountFreezeService.updateByQuery(MapUtils.of("xid",freeze.getXid()), MapUtils.of("freezeMoney",0,"state",AccountFreeze.State.CANCEL)); return count == 1; }

    测试:

    @RestController
    @RequestMapping("account")
    public class AccountController {
        @Autowired
        private AccountService accountService;
        @Autowired
        private TccHandler tccHandler;
        @PutMapping("/{userId}/{money}")
        public String deduct(@PathVariable("userId") String userId, @PathVariable("money") Integer money,Boolean isExp){
            tccHandler.deduct(userId,money,isExp);
            return "ok";
        }
        @PostMapping("addAccount")
        public ResponseEntity<Void> add(@RequestBody  Account account){
            accountService.saveOrUpdate(account);
            return ResponseEntity.noContent().build();
        }
    }

    1.创建账户初始化money 100

    curl -X POST "http://localhost:7302/account/addAccount" -H "accept: */*" -H "Content-Type: application/json" -d "{ \"id\": \"1\", \"money\": 100, \"userId\": \"1\"}"

    2. 冻结10 用户1, 10元 ,   查看DB , 扣减正常

    curl -X PUT "http://localhost:7302/account/1/10?isExp=flase" -H "accept: */*"

    3. 手动制造异常,查看是否能正常回滚

    curl -X PUT "http://localhost:7302/account/1/10?isExp=true" -H "accept: */*"

    说明, try  方法可以传递参数到ctx

    eg: 

        @TwoPhaseBusinessAction(name = "TccActionTwo", commitMethod = "commit", rollbackMethod = "rollback")
        public boolean prepare(BusinessActionContext actionContext,
                               @BusinessActionContextParameter(paramName = "b") String b,
                               @BusinessActionContextParameter(paramName = "c", index = 1) List list);
     然后 可以通过 actionContext 在 confirm or cancal 方法中获取
  • 相关阅读:
    Java实现继承过程概述
    Java封装概述
    Java中包的介绍
    Java中final关键字概述
    Java继承概述
    mysql服务自动关闭的解决
    Unity3d碰撞检测中碰撞器与触发器的区别
    解决在Game模式下兼容编辑器模式
    GameObject.Find("")只能查找到显示的对象
    层(layer)的设置
  • 原文地址:https://www.cnblogs.com/lshan/p/16541300.html
Copyright © 2020-2023  润新知