Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

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

Open
Hakunata opened this issue Feb 27, 2022 · 7 comments

Comments

@Hakunata
Copy link

Hakunata commented Feb 27, 2022

转出服务的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);
}`
@WillemJiang
Copy link
Member

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.

@Hakunata
Copy link
Author

Hakunata commented Feb 28, 2022

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

@Hakunata
Copy link
Author

Hakunata commented Feb 28, 2022

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).

@WillemJiang
Copy link
Member

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.

@Hakunata
Copy link
Author

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
Copy link
Author

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.

@WillemJiang
Copy link
Member

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants