TX-LCN分布式事務之LCN模式


什么是LCN模式

LCN模式是TX-LCN分布式事務模式的一種,L-lock-鎖定事務單元、C-confirm-確認事務模塊狀態、 notify-通知事務單元

原理

LCN模式是通過Spring AOP的方式代理Connection的方式實現對本地事務的操作,然后在由TxManager統一協調控制事務。 當本地事務提交回滾或者關閉連接時將會執行假操作,該代理的連接將由LCN連接池管理。

模式特點

  • 該模式對代碼的嵌入性為低。
  • 該模式僅限於本地存在連接對象且可通過連接對象控制事務的模塊。
  • 該模式下的事務提交與回滾是由本地事務方控制,對於數據一致性上有較高的保障。
  • 該模式缺陷在於代理的連接需要隨事務發起方一共釋放連接,增加了連接占用的時間。

源碼解讀

首先我們來看幾個關鍵的類DataSourceAspect-數據源切面類、TransactionAspect事務切面類、LcnConnectionProxylcn 連接代理類、DTXLogicWeaver分布式事務調度器、DTXServiceExecutor分布式事務執行器

DataSourceAspect的作用

  • 源碼
@Aspect
@Component
public class DataSourceAspect implements Ordered {
    private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
    private final TxClientConfig txClientConfig;
    private final DTXResourceWeaver dtxResourceWeaver;

    public DataSourceAspect(TxClientConfig txClientConfig, DTXResourceWeaver dtxResourceWeaver) {
        this.txClientConfig = txClientConfig;
        this.dtxResourceWeaver = dtxResourceWeaver;
    }

    @Around("execution(* javax.sql.DataSource.getConnection(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        return this.dtxResourceWeaver.getConnection(() -> {
            return (Connection)point.proceed();
        });
    }

    public int getOrder() {
        return this.txClientConfig.getResourceOrder();
    }
}

由該類的源碼,我們能夠知道,lcn模式主要對數據庫的連接進行了攔截代理。獲取到數據庫的連接交由lcn 來進行代理。

TransactionAspect 作用

  • 源碼
@Aspect
@Component
public class TransactionAspect implements Ordered {
    private static final Logger log = LoggerFactory.getLogger(TransactionAspect.class);
    private final TxClientConfig txClientConfig;
    private final DTXLogicWeaver dtxLogicWeaver;

    public TransactionAspect(TxClientConfig txClientConfig, DTXLogicWeaver dtxLogicWeaver) {
        this.txClientConfig = txClientConfig;
        this.dtxLogicWeaver = dtxLogicWeaver;
    }
    
    @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.LcnTransaction)")
    public void lcnTransactionPointcut() {
    }

    @Around("lcnTransactionPointcut() && !txcTransactionPointcut()&& !tccTransactionPointcut() && !txTransactionPointcut()")
    public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable {
        DTXInfo dtxInfo = DTXInfo.getFromCache(point);
        LcnTransaction lcnTransaction = (LcnTransaction)dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
        dtxInfo.setTransactionType("lcn");
        dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
        DTXLogicWeaver var10000 = this.dtxLogicWeaver;
        point.getClass();
        return var10000.runTransaction(dtxInfo, point::proceed);
    }

    public int getOrder() {
        return this.txClientConfig.getDtxAspectOrder();
    }
}

由該類的源碼,我們能夠明白,通過解析@LcnTransaction注解進行相應的操作。代碼會調用到DTXLogicWeaver

DTXLogicWeaver 作用

    public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {
        if (Objects.isNull(DTXLocalContext.cur())) {
            DTXLocalContext.getOrNew();
            log.debug("<---- TxLcn start ---->");
            DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
            TxContext txContext;
            if (this.globalContext.hasTxContext()) {
                txContext = this.globalContext.txContext();
                dtxLocalContext.setInGroup(true);
                log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
            } else {
                txContext = this.globalContext.startTx();
            }

            if (Objects.nonNull(dtxLocalContext.getGroupId())) {
                dtxLocalContext.setDestroy(false);
            }

            dtxLocalContext.setUnitId(dtxInfo.getUnitId());
            dtxLocalContext.setGroupId(txContext.getGroupId());
            dtxLocalContext.setTransactionType(dtxInfo.getTransactionType());
            TxTransactionInfo info = new TxTransactionInfo();
            info.setBusinessCallback(business);
            info.setGroupId(txContext.getGroupId());
            info.setUnitId(dtxInfo.getUnitId());
            info.setPointMethod(dtxInfo.getBusinessMethod());
            info.setPropagation(dtxInfo.getTransactionPropagation());
            info.setTransactionInfo(dtxInfo.getTransactionInfo());
            info.setTransactionType(dtxInfo.getTransactionType());
            info.setTransactionStart(txContext.isDtxStart());
            boolean var15 = false;

            Object var6;
            try {
                var15 = true;
                var6 = this.transactionServiceExecutor.transactionRunning(info);
                var15 = false;
            } finally {
                if (var15) {
                    if (dtxLocalContext.isDestroy()) {
                        synchronized(txContext.getLock()) {
                            txContext.getLock().notifyAll();
                        }

                        if (!dtxLocalContext.isInGroup()) {
                            this.globalContext.destroyTx();
                        }

                        DTXLocalContext.makeNeverAppeared();
                        TracingContext.tracing().destroy();
                    }

                    log.debug("<---- TxLcn end ---->");
                }
            }

            if (dtxLocalContext.isDestroy()) {
                synchronized(txContext.getLock()) {
                    txContext.getLock().notifyAll();
                }

                if (!dtxLocalContext.isInGroup()) {
                    this.globalContext.destroyTx();
                }

                DTXLocalContext.makeNeverAppeared();
                TracingContext.tracing().destroy();
            }

            log.debug("<---- TxLcn end ---->");
            return var6;
        } else {
            return business.call();
        }
    }

以上代碼是該類的核心邏輯,可以看出來TX-LCN事務的處理全部都是走的這個類的該方法,最終會調用到DTXServiceExecutor分布式事務執行器

DTXServiceExecutor 作用


    /**
     * 事務業務執行
     *
     * @param info info
     * @return Object
     * @throws Throwable Throwable
     */

    public Object transactionRunning(TxTransactionInfo info) throws Throwable {

        // 1. 獲取事務類型
        String transactionType = info.getTransactionType();

        // 2. 獲取事務傳播狀態
        DTXPropagationState propagationState = propagationResolver.resolvePropagationState(info);

        // 2.1 如果不參與分布式事務立即終止
        if (propagationState.isIgnored()) {
            return info.getBusinessCallback().call();
        }

        // 3. 獲取本地分布式事務控制器
        DTXLocalControl dtxLocalControl = txLcnBeanHelper.loadDTXLocalControl(transactionType, propagationState);

        // 4. 織入事務操作
        try {
            // 4.1 記錄事務類型到事務上下文
            Set<String> transactionTypeSet = globalContext.txContext(info.getGroupId()).getTransactionTypes();
            transactionTypeSet.add(transactionType);

            dtxLocalControl.preBusinessCode(info);

            // 4.2 業務執行前
            txLogger.txTrace(
                    info.getGroupId(), info.getUnitId(), "pre business code, unit type: {}", transactionType);

            // 4.3 執行業務
            Object result = dtxLocalControl.doBusinessCode(info);

            // 4.4 業務執行成功
            txLogger.txTrace(info.getGroupId(), info.getUnitId(), "business success");
            dtxLocalControl.onBusinessCodeSuccess(info, result);
            return result;
        } catch (TransactionException e) {
            txLogger.error(info.getGroupId(), info.getUnitId(), "before business code error");
            throw e;
        } catch (Throwable e) {
            // 4.5 業務執行失敗
            txLogger.error(info.getGroupId(), info.getUnitId(), Transactions.TAG_TRANSACTION,
                    "business code error");
            dtxLocalControl.onBusinessCodeError(info, e);
            throw e;
        } finally {
            // 4.6 業務執行完畢
            dtxLocalControl.postBusinessCode(info);
        }
    }

通過以上代碼可以看出,該類是整個事務執行關鍵類。

以上就是LCN模式比較核心的代碼,其他的分支代碼就不一一贅述了

實戰

由上一篇分布式事務之TX-LCN 我們規划了倆個TC分別是lcn-order 服務和lcn-pay服務,我們的思路是訂單服務調用支付服務,分別在訂單服務表t_order和支付服務表t_pay中插入插入數據。

訂單服務核心代碼和數據表腳本

  • 代碼
/**
 * @author:triumphxx
 * @Date:2021/10/24
 * @Time:2:13 下午
 * @微信公眾號:北漂碼農有話說
 * @網站:http://blog.triumphxx.com.cn
 * @GitHub https://github.com/triumphxx
 * @Desc:
 **/

@RestController
public class LcnOrderController {

    @Autowired
    TOrderDao tOrderDao;

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/add-order")
    @Transactional(rollbackFor = Exception.class)
    @LcnTransaction
    public String add()
{
        TOrder bean = new TOrder();
        bean.setTId(1);
        bean.setTName("order");
        restTemplate.postForEntity("http://lcn-pay/add-pay","",String.class);
//        int i = 1/0;
        tOrderDao.insert(bean);
        return "新增訂單成功";
    }
}
  • 腳本
CREATE TABLE `t_order` (
   `t_id` int(11NOT NULL,
   `t_name` varchar(45DEFAULT NULL
ENGINE=InnoDB DEFAULT CHARSET=latin1

支付服務核心代碼和數據表腳本

  • 代碼
/**
 * @author:triumphxx
 * @Date:2021/10/24
 * @Time:2:26 下午
 * @微信公眾號:北漂碼農有話說
 * @網站:http://blog.triumphxx.com.cn
 * @GitHub https://github.com/triumphxx
 * @Desc:
 **/

@RestController
public class LcnPayController {
    @Autowired
    TPayDao tPayDao;

    @PostMapping("/add-pay")
    @Transactional(rollbackFor = Exception.class)
    @LcnTransaction
    public String addPay()
{
        TPay tPay = new TPay();
        tPay.setTId(1);
        tPay.setTName("t_pay");
        int i = tPayDao.insertSelective(tPay);
        return "新增支付成功";

    }
}
  • 腳本
CREATE TABLE `t_pay` (
     `t_id` int(11NOT NULL,
     `t_name` varchar(45DEFAULT NULL
ENGINE=InnoDB DEFAULT CHARSET=latin1

測試流程

  • 啟動 Redis
  • 啟動 TM
  • 啟動注冊中心 eureka-server
  • 啟動服務 lcn-order
  • 啟動服務 lcn-pay
  • 請求接口 http://localhost:8001/add-order
  • 代碼創造異常看數據是否進行回滾

小結

本篇我們分析了TX-LCN分布式事務的lcn模式的原理及相關源碼,以及搭建服務的進行測試。希望能對大家有所幫助。 源碼地址源碼傳送門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM