liuyangming / ByteTCC

ByteTCC is a distributed transaction manager based on the TCC(Try/Confirm/Cancel) mechanism. It’s compatible with the JTA specification. User guide: https://github.com/liuyangming/ByteTCC/wiki
https://www.bytesoft.org/
GNU Lesser General Public License v3.0
2.9k stars 911 forks source link

当不使用Controller实现Service的try阶段,造成的cancel方法不被调用问题 #33

Open casoc opened 6 years ago

casoc commented 6 years ago

问题描述

在使用demo项目provider和consumer过程中,我测试各个阶段抛出异常的处理逻辑,逻辑都是没有问题,但是直接在Controller层实现了Service接口的try阶段方法这种做法,私以为不够优雅,于是新写了一个Service实现类,实现了try阶段逻辑,并在Controller层注入使用,代码如下:

AccountServiceImpl.java

@Service("accountService")
@Compensable(interfaceClass = IAccountService.class, confirmableKey = "accountServiceConfirm", cancellableKey = "accountServiceCancel")
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void increaseAmount(String accountId, double amount) {
        int value = this.jdbcTemplate.update("update tb_account_one set frozen = frozen + ? where acct_id = ?", amount, accountId);
        if (value != 1) {
            throw new IllegalStateException("ERROR!");
        }
        System.out.printf("exec increase: acct= %s, amount= %7.2f%n", accountId, amount);
    }

    @Transactional
    public void decreaseAmount(String accountId, double amount) {
        int value = this.jdbcTemplate.update("update tb_account_one set amount = amount - ?, frozen = frozen + ? where acct_id = ?", amount, amount, accountId);
        if (value != 1) {
            throw new IllegalStateException("ERROR!");
        }
        System.out.printf("exec decrease: acct= %s, amount= %7.2f%n", accountId, amount);

        // throw new IllegalStateException("error");
    }
}

AccountController.java

@RestController
public class AccountController {

    @Resource(name = "accountService")
    private IAccountService iAccountService;

    @ResponseBody
    @RequestMapping(value = "/increase", method = RequestMethod.POST)
    public void increaseAmount(@RequestParam("acctId") String acctId, @RequestParam("amount") double amount) {
        iAccountService.increaseAmount(acctId, amount);
    }

    @ResponseBody
    @RequestMapping(value = "/decrease", method = RequestMethod.POST)
    public void decreaseAmount(@RequestParam("acctId") String acctId, @RequestParam("amount") double amount) {
        iAccountService.decreaseAmount(acctId, amount);
    }

}

如果改成这样之后当在consumer的try阶段抛出异常,provider的try阶段和confirm阶段都会被调用,并且cancel阶段不会被调用,猜想可能的事务传播问题,于是在Controller的方法上也都加上@Transactional,之后在前面的那种情况下provider的confirm阶段不会再被调用,但是仍然没有调用cancel补偿provider的try阶段。

最终将Service上的@Compensable注解移动到Controller上一切恢复正常

即使用如下代码: AccountServiceImpl.java

@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void increaseAmount(String accountId, double amount) {
        int value = this.jdbcTemplate.update("update tb_account_one set frozen = frozen + ? where acct_id = ?", amount, accountId);
        if (value != 1) {
            throw new IllegalStateException("ERROR!");
        }
        System.out.printf("exec increase: acct= %s, amount= %7.2f%n", accountId, amount);
    }

    @Transactional
    public void decreaseAmount(String accountId, double amount) {
        int value = this.jdbcTemplate.update("update tb_account_one set amount = amount - ?, frozen = frozen + ? where acct_id = ?", amount, amount, accountId);
        if (value != 1) {
            throw new IllegalStateException("ERROR!");
        }
        System.out.printf("exec decrease: acct= %s, amount= %7.2f%n", accountId, amount);

        // throw new IllegalStateException("error");
    }
}

AccountController.java

@Compensable(interfaceClass = IAccountService.class, confirmableKey = "accountServiceConfirm", cancellableKey = "accountServiceCancel")
@RestController
public class AccountController {

    @Resource(name = "accountService")
    private IAccountService iAccountService;

    @ResponseBody
    @RequestMapping(value = "/increase", method = RequestMethod.POST)
    @Transactional
    public void increaseAmount(@RequestParam("acctId") String acctId, @RequestParam("amount") double amount) {
        iAccountService.increaseAmount(acctId, amount);
    }

    @ResponseBody
    @RequestMapping(value = "/decrease", method = RequestMethod.POST)
    @Transactional
    public void decreaseAmount(@RequestParam("acctId") String acctId, @RequestParam("amount") double amount) {
        iAccountService.decreaseAmount(acctId, amount);
    }

}

PS consumer项目的SQL少了frozen字段

liuyangming commented 6 years ago

你所列举的场景中,如果要将实际处理逻辑放到AccountServiceImpl,也需要在AccountController上添加@Compensable注解(confirmableKey&cancellabkeKey可以不设置,但interfaceClass仍需要有),也就是说AccountController & AccountServiceImpl都需要加@Compensable注解。

ByteTCC之所以这样做,是因为使用SpringCloud时实际对外提供服务的其实是AccountController,因此定义服务时,ByteTCC 0.4.x版本强制要求注明其为TCC服务,否则将以普通事务对待。

后续版本将考虑适当放宽这个限制。。。