servicecomb-pack icon indicating copy to clipboard operation
servicecomb-pack copied to clipboard

转账场景:转出服务confirm阶段判断金额超出,抛出异常,本地事务回滚了,全局事务没有收到异常,全局事务被成功了

Open Hakunata opened this issue 2 years ago • 7 comments

转出服务的confirm方法: `

@Transactional
public UserAccount confirm(TransferEntity entity){
    UserAccount userAccount = accountMapper.selectByIdForUpdate(entity.getId());
    if (userAccount.getAmount().compareTo(new BigDecimal(entity.getMoney().toString())) == -1){
        // *******************************************
        // 这里抛出异常了,全局事务无法感知,prepare阶段是可以的,所以也无法调到后面的cancel,造成成功假象
        // *******************************************
        throw new IllegalArgumentException("Transfer failed,not enough money");
    }
    userAccount.setAmount(userAccount.getAmount().subtract(new BigDecimal(entity.getMoney().toString())));
    userAccount.setFreezedAmount(userAccount.getFreezedAmount().subtract(new BigDecimal(entity.getMoney().toString())));
    accountMapper.updateById(userAccount);
    return userAccount;
}`

全局事务没有收到错误或异常,造成全局事务执行成功的错误结果: `

@TccStart
public Result transfer(TransferEntity transferEntity){
    String errMsg = "";
    Integer errCode = 0;
    // 调用转出微服务
    try{
        // *******************************************
        // 其实转出服务confirm阶段异常回滚了,但是这里不知道,因为拿到一个错误的结果了
        // *******************************************
        ResponseEntity<UserAccount> outReuslt = alphaClient.getForEntity(getBankAlphaURL(transferEntity),UserAccount.class,transferEntity.getFromId(),transferEntity.getMoney() );
        System.out.println(outReuslt.getBody());
    }catch (Exception e){
        e.printStackTrace();
        throw e;
    }
    // 调用转出微服务
    try{
        // *******************************************
        // 转入服务继续执行,其实转出confirm阶段已经出错回滚了
        // *******************************************
        ResponseEntity<UserAccount> inReuslt = betaClient.getForEntity(getBankBetaURL(transferEntity),UserAccount.class,transferEntity.getToId(),transferEntity.getMoney() );
        System.out.println(inReuslt.getBody());
    }catch (Exception e){
        e.printStackTrace();
        throw e;
    }
    return new Result(errCode,errMsg);
}`

Hakunata avatar Feb 27 '22 16:02 Hakunata

Normally we don't call any cancel method if the confirm method is failed. Otherwise it will make recover process very complicated.
You need to check do the balance check in the sub transaction prepare method, and confirm method just used to committed the transaction, and we can do other check later.

WillemJiang avatar Feb 28 '22 01:02 WillemJiang

So,if meet error on "confirm" method,what we shuold do for the next

Hakunata avatar Feb 28 '22 01:02 Hakunata

Normally we don't call any cancel method if the confirm method is failed. Otherwise it will make recover process very complicated. You need to check do the balance check in the sub transaction prepare method, and confirm method just used to committed the transaction, and we can do other check later.

I think if we do not check and protect on "confirm" method(like check balance),there would be problems on concurrent requests context(like multiple requests transfer out money and substract balance).

Hakunata avatar Feb 28 '22 01:02 Hakunata

Just like the calling commit method with JDBC, it could be failed,we can do some other recovery work later.

My suggestion is we check the balance or even withdraw some money from the account in the prepare method. If some thing wrong with the whole transaction, we can still do some recovery work in cancel method.

If the cancel method call is failed, we need to do some manually recovery work with more detail transaction context information then.

In this way , we can make our system more easy to maintain.

WillemJiang avatar Feb 28 '22 02:02 WillemJiang

Just like the calling commit method with JDBC, it could be failed,we can do some other recovery work later.

My suggestion is we check the balance or even withdraw some money from the account in the prepare method. If some thing wrong with the whole transaction, we can still do some recovery work in cancel method.

If the cancel method call is failed, we need to do some manually recovery work with more detail transaction context information then.

In this way , we can make our system more easy to maintain.

It looks more like SAGA rather than TCC.

Hakunata avatar Feb 28 '22 02:02 Hakunata

Just like the calling commit method with JDBC, it could be failed,we can do some other recovery work later.

My suggestion is we check the balance or even withdraw some money from the account in the prepare method. If some thing wrong with the whole transaction, we can still do some recovery work in cancel method.

If the cancel method call is failed, we need to do some manually recovery work with more detail transaction context information then.

In this way , we can make our system more easy to maintain.

Would you supplied me some success case for other company or project? There is some doubt about that we could use this framework for my product system.

Hakunata avatar Feb 28 '22 02:02 Hakunata

Current , we just do some coordinations between different sub transactions by leveraging the framework that Pack provides. TCC and Saga just provide two different ways for us to do some coordination work for multiple micro services calling.

There are some timeout and retry policies we can apply to the Omega agent for the best effort call, but it dependents on the user scenario details.

WillemJiang avatar Feb 28 '22 02:02 WillemJiang