TCC模式與AT模式非常相似,每階段都是獨立事務,不同的是TCC通過人工編碼來實現數據恢復。需要實現三個方法:
圖片
階段一(Try):檢查余額是否充足,如果充足則凍結金額增加30元,可用余額扣除30
圖片
圖片
此時,總金額 = 凍結金額 + 可用金額,數量依然是100不變,事務直接提交無需等待其它事務。
階段二(Confirm) :假如要提交,則凍結金額扣減30
圖片
確認可以提交,不過之前可用金額已經扣減過了,這里只要清除凍結金額就好了,此時,總金額 = 凍結金額 + 可用金額 = 0 + 70 = 70
階段二(Cancel):如果要回滾,則凍結金額扣減30,可用余額增加30
圖片
需要回滾,那么就要釋放凍結金額,恢復可用金額
圖片
配置和依賴參考之前《利用Nacos實現Seata事務模式(XA與AT)的快速配置與靈活切換》即可
bank3:
聲明TCC接口@LocalTCCpublic interface AccountInTcc { @TwoPhaseBusinessAction(name = "prepareDeductMoney", commitMethod = "commitDeductMoney", rollbackMethod = "rollbackDeductMoney") boolean prepareDeductMoney(BusinessActionContext businessActionContext, @BusinessActionContextParameter(paramName = "accountNo")String accountNo, @BusinessActionContextParameter(paramName = "amount")Double amount); /** * 提交扣款 * 二階段confirm確認方法、可以另命名,但要保證與commitMethod一致 */ boolean commitDeductMoney(BusinessActionContext businessActionContext); /** * 回滾扣款 * 二階段回滾方法,要保證與rollbackMethod一致 */ boolean rollbackDeductMoney(BusinessActionContext businessActionContext);}
具體實現:
@Componentpublic class AccountInTccImpl implements AccountInTcc { @Autowired private AccountInfoMapper accountInfoMapper; @Transactional @Override public boolean prepareDeductMoney(BusinessActionContext businessActionContext, String accountNo, Double amount) { String xid = businessActionContext.getXid(); // 冪等性判斷 if (TccActionResultWrap.hasPrepareResult(xid)) { return true; } // 避免空懸掛,已經執行過回滾了就不能再預留資源 if (TccActionResultWrap.hasRollbackResult(xid) || TccActionResultWrap.hasCommitResult(xid)) { return false; } // 預留資源 boolean result = accountInfoMapper.prepareDeductMoney(accountNo,amount) > 0; // 記錄執行結果,以便回滾時判斷是否是空回滾 TccActionResultWrap.prepareSuccess(xid); System.out.println("============prepare=============="); return result; } // 保證提交邏輯的原子性 @Transactional @Override public boolean commitDeductMoney(BusinessActionContext businessActionContext) { String xid = businessActionContext.getXid(); // 冪等性判斷 if (TccActionResultWrap.hasCommitResult(xid)) { return true; } Map<String, Object> actionContext = businessActionContext.getActionContext(); String accountNo = (String) actionContext.get("accountNo"); BigDecimal amount = (BigDecimal) actionContext.get("amount"); // 執行提交操作,扣除預留款 boolean result = accountInfoMapper.commitDeductMoney(accountNo,amount.doubleValue()) > 0; // 清除預留結果 TccActionResultWrap.removePrepareResult(xid); // 設置提交結果 TccActionResultWrap.commitSuccess(xid); System.out.println("============commit=============="); return result; } @Transactional @Override public boolean rollbackDeductMoney(BusinessActionContext businessActionContext) { String xid = businessActionContext.getXid(); // 冪等性判斷 if (TccActionResultWrap.hasRollbackResult(xid)) { return true; } // 沒有預留資源結果,回滾不做任何處理; if (!TccActionResultWrap.hasPrepareResult(xid)) { // 設置回滾結果,防止空回滾 TccActionResultWrap.rollbackSuccess(xid); return true; } // 執行回滾 Map<String, Object> actionContext = businessActionContext.getActionContext(); String accountNo = (String) actionContext.get("accountNo"); BigDecimal amount = (BigDecimal) actionContext.get("amount"); boolean result = accountInfoMapper.rollbackDeductMoney(accountNo,amount.doubleValue()) > 0; // 清除預留結果 TccActionResultWrap.removePrepareResult(xid); // 設置回滾結果 TccActionResultWrap.rollbackSuccess(xid); System.out.println("============rollback=============="); return result; }}
業務層:
@Autowired private AccountInTcc accountInTcc; @Override public Boolean deductMoney(String accountNo, Double amount) { return accountInTcc.prepareDeductMoney(null,accountNo,amount); }
參數中的BusinessActionContext不需要開發人員自己傳遞,直接給null即可,Seata會自動處理。
mapper:
@Update("update account_info set account_balance = account_balance - #{amount}, frozen_money = frozen_money + #{amount} where account_no = #{accountNo} and account_balance >= #{amount}") int prepareDeductMoney(@Param("accountNo") String accountNo, @Param("amount") Double amount); @Update("update account_info set frozen_money = frozen_money - #{amount} where account_no = #{accountNo}") int commitDeductMoney(@Param("accountNo") String accountNo, @Param("amount") Double amount); @Update("update account_info set account_balance = account_balance + #{amount}, frozen_money = frozen_money - #{amount} where account_no = #{accountNo}") int rollbackDeductMoney(@Param("accountNo") String accountNo, @Param("amount") Double amount);
bank4服務調用:
@GlobalTransactional @Override public Boolean addMoney(String accountNo, Double amount) { String result = bank3Client.deduct(amount); if("true".equalsIgnoreCase(result)){ Boolean flag = baseMapper.addMoney(accountNo,amount) > 0; if(amount != 30 ) throw new RuntimeException("bank4 make exception amount != 30"); return flag; } return false; }
TCC的優點:
TCC的缺點:
圖片
執行cancel操作時,應當判斷try是否已經執行,如果尚未執行,則應該空回滾。
執行try操作時,應當判斷cancel是否已經執行過了,如果已經執行,應當阻止空回滾后的try操作,避免懸掛。
本文鏈接:http://www.www897cc.com/showinfo-26-70394-0.html借助Nacos高效配置與實踐Seata事務的TCC模式
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: PHP 高性能的事件循環庫 Revolt
下一篇: Vue3問題:如何實現頁面引導提示?