欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Seata AT模式TransactionHook被刪除探究

 更新時間:2022年11月15日 10:25:06   作者:夢想實現(xiàn)家_Z  
這篇文章主要為大家介紹了Seata AT模式TransactionHook被刪除探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

兄弟們,剛剛又給seata社區(qū)修了一個BUG,有用戶提了issue反應(yīng)TransactionHook在某些情況下不會被調(diào)用:

相關(guān)issue鏈接:github.com/seata/seata…,該用戶在issue中已經(jīng)指出了相關(guān)問題所在:

下面我們來看一下到底是什么原因?qū)е铝松鲜?code>BUG的產(chǎn)生。

問題定位

根據(jù)用戶的反饋,我們找到目標(biāo)源碼io.seata.tm.api.TransactionalTemplate#execute()

try {
    // 開啟分布式事務(wù),獲取XID         
    beginTransaction(txInfo, tx);
    Object rs;
    try {
        // 執(zhí)行業(yè)務(wù)代碼
        rs = business.execute();
    } catch (Throwable ex) {
        // 3. 處理異常,準(zhǔn)備回滾.
        completeTransactionAfterThrowing(txInfo, tx, ex);
        throw ex;
    }
    // 4. 提交事務(wù).
    commitTransaction(tx, txInfo);
    return rs;
} finally {
    //5. 回收現(xiàn)場
    resumeGlobalLockConfig(previousConfig);
    triggerAfterCompletion();
    cleanUp();
}

問題代碼就出在cleanUp()中,我們來看一下里面做了什么操作,最終我們定位到:

public final class TransactionHookManager {
  private static final ThreadLocal<List<TransactionHook>> LOCAL_HOOKS = new ThreadLocal<>();
  // 注冊TransactionHook
  public static void registerHook(TransactionHook transactionHook) {
      if (transactionHook == null) {
            throw new NullPointerException("transactionHook must not be null");
        }
        List<TransactionHook> transactionHooks = LOCAL_HOOKS.get();
        if (transactionHooks == null) {
            LOCAL_HOOKS.set(new ArrayList<>());
        }
        LOCAL_HOOKS.get().add(transactionHook);
    }
  // 移除當(dāng)前線程上所有TransactionHook
  public static void clear() {
      LOCAL_HOOKS.remove();
  }
}

由上面的源碼可知,cleanUp()操作時把當(dāng)前線程中的所有TransactionHook都清除掉了。也就是說,假如事務(wù)A和事務(wù)B共用同一個線程,當(dāng)事務(wù)B處理完畢后,調(diào)用了cleanUp()回收現(xiàn)場時,把該線程當(dāng)中存儲的所有TransactionHook全部清除掉了,導(dǎo)致事務(wù)A的生命周期中找不到該事務(wù)對應(yīng)的TransactionHook,從而產(chǎn)生了BUG。

如何解決

通過與seata社區(qū)的大佬不斷地溝通,最終敲定以下方案:

1.改造TransactionHookManager.LOCAL_HOOKS,把數(shù)據(jù)類型改成ThreadLocal<Map<String, List<TransactionHook>>>,Map中的key對應(yīng)分布式事務(wù)XID;

2.針對當(dāng)前上下文中沒有XID,那么key就為null,因為HashMap允許keynull;

3.當(dāng)用戶查詢指定XID下的hook時,連同keynull對應(yīng)的hook也一起返回;

  • 第一步比較好理解,因為事務(wù)A和事務(wù)B對應(yīng)的TransactionHook沒有被區(qū)分出來,所以造成了清理事務(wù)B的TransactionHook時連同事務(wù)A的TransactionHook一起被清除,那么我們修改數(shù)據(jù)結(jié)構(gòu)來區(qū)分事務(wù)A和事務(wù)B的TransactionHook,以便清理的時候不會造成誤刪;

第二步為什么要針對沒有XID的時候也要能設(shè)置TransactionHook,因為有這么一段代碼:

    private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
        try {
            // 執(zhí)行triggerBeforeBegin()
            triggerBeforeBegin();
            // 注冊分布式事務(wù),生成XID
            tx.begin(txInfo.getTimeOut(), txInfo.getName());
            // 執(zhí)行triggerAfterBegin()
            triggerAfterBegin();
        } catch (TransactionException txe) {
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.BeginFailure);
        }
    }

上面的代碼會產(chǎn)生一個問題,因為我們的TransactionHook依賴于XID,但是triggerBeforeBegin()執(zhí)行的時候還沒有產(chǎn)生XID,所以為了能夠在沒有XID的時候也能夠讓TransactionHook生效,我們要有一個虛值key來臨時設(shè)置TransactionHook;

第三步的設(shè)計時為了在第二步的基礎(chǔ)上,當(dāng)事務(wù)開啟后獲取XID后,要保證XID獲取前注冊的TransactionHook也要生效,我們在通過XID查詢TransactionHook時要把虛值key對應(yīng)的TransactionHook也一起返回;

注意事項

在實際代碼修改中,發(fā)現(xiàn)triggerAfterCommit()、triggerAfterRollback()、triggerAfterCompletion()在被調(diào)用時始終拿不到對應(yīng)的TransactionHook,最終debug下來發(fā)現(xiàn)在調(diào)用這三個方法前,上下文中的XID被解綁了,導(dǎo)致拿到的XID為空。代碼類似下面這樣:

try {
            // 調(diào)用triggerBeforeCommit()
            triggerBeforeCommit();
            // 提交事務(wù),清除XID
            tx.commit();
            if (Arrays.asList(GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbacked).contains(tx.getLocalStatus())) {
                throw new TransactionalExecutor.ExecutionException(tx,
                        new TimeoutException(String.format("Global transaction[%s] is timeout and will be rollback[TC].", tx.getXid())),
                        TransactionalExecutor.Code.TimeoutRollback);
            }
            // 調(diào)用triggerAfterCommit()
            triggerAfterCommit();
        } catch (TransactionException txe) {
            // 4.1 Failed to commit
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.CommitFailure);
        }

不過經(jīng)過我的一番查找,發(fā)現(xiàn)GlobalTransaction中是包含XID屬性的,所以果斷從GlobalTransaction對象中取XID傳進(jìn)來。

修改后的代碼如下:

try {
            // 調(diào)用triggerBeforeCommit()
            triggerBeforeCommit();
            // 提交事務(wù),清除XID
            tx.commit();
            if (Arrays.asList(GlobalStatus.TimeoutRollbacking, GlobalStatus.TimeoutRollbacked).contains(tx.getLocalStatus())) {
                throw new TransactionalExecutor.ExecutionException(tx,
                        new TimeoutException(String.format("Global transaction[%s] is timeout and will be rollback[TC].", tx.getXid())),
                        TransactionalExecutor.Code.TimeoutRollback);
            }
            // 調(diào)用triggerAfterCommit()
            triggerAfterCommit(tx.getXid());
        } catch (TransactionException txe) {
            // 4.1 Failed to commit
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.CommitFailure);
        }

改造后的TransactionHookManager

public final class TransactionHookManager {
    private TransactionHookManager() {
    }
    private static final ThreadLocal<Map<String, List<TransactionHook>>> LOCAL_HOOKS = new ThreadLocal<>();
    /**
     * get the current hooks
     *
     * @return TransactionHook list
     */
    public static List<TransactionHook> getHooks() {
        String xid = RootContext.getXID();
        return getHooks(xid);
    }
    /**
     * get hooks by xid
     * 
     * @param xid
     * @return TransactionHook list
     */
    public static List<TransactionHook> getHooks(String xid) {
        Map<String, List<TransactionHook>> hooksMap = LOCAL_HOOKS.get();
        if (hooksMap == null || hooksMap.isEmpty()) {
            return Collections.emptyList();
        }
        List<TransactionHook> hooks = new ArrayList<>();
        List<TransactionHook> localHooks = hooksMap.get(xid);
        if (StringUtils.isNotBlank(xid)) {
            List<TransactionHook> virtualHooks = hooksMap.get(null);
            if (virtualHooks != null && !virtualHooks.isEmpty()) {
                hooks.addAll(virtualHooks);
            }
        }
        if (localHooks != null && !localHooks.isEmpty()) {
            hooks.addAll(localHooks);
        }
        if (hooks.isEmpty()) {
            return Collections.emptyList();
        }
        return Collections.unmodifiableList(hooks);
    }
    /**
     * add new hook
     *
     * @param transactionHook transactionHook
     */
    public static void registerHook(TransactionHook transactionHook) {
        if (transactionHook == null) {
            throw new NullPointerException("transactionHook must not be null");
        }
        Map<String, List<TransactionHook>> hooksMap = LOCAL_HOOKS.get();
        if (hooksMap == null) {
            hooksMap = new HashMap<>();
            LOCAL_HOOKS.set(hooksMap);
        }
        String xid = RootContext.getXID();
        List<TransactionHook> hooks = hooksMap.get(xid);
        if (hooks == null) {
            hooks = new ArrayList<>();
            hooksMap.put(xid, hooks);
        }
        hooks.add(transactionHook);
    }
    /**
     * clear hooks by xid
     * 
     * @param xid
     */
    public static void clear(String xid) {
        Map<String, List<TransactionHook>> hooksMap = LOCAL_HOOKS.get();
        if (hooksMap == null || hooksMap.isEmpty()) {
            return;
        }
        hooksMap.remove(xid);
        if (StringUtils.isNotBlank(xid)) {
            hooksMap.remove(null);
        }
    }
}

以上就是Seata AT模式TransactionHook被刪除探究的詳細(xì)內(nèi)容,更多關(guān)于Seata AT刪除TransactionHook的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java實現(xiàn)的DES加密算法詳解

    java實現(xiàn)的DES加密算法詳解

    這篇文章主要介紹了java實現(xiàn)的DES加密算法,結(jié)合實例形式詳細(xì)分析了java實現(xiàn)DES加密操作的原理、實現(xiàn)技巧與相關(guān)注意事項,需要的朋友可以參考下
    2017-06-06
  • 將Java項目打包成可執(zhí)行的jar包

    將Java項目打包成可執(zhí)行的jar包

    這篇文章主要介紹了將Java項目打包成可執(zhí)行的jar包,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Java Document生成和解析XML操作

    Java Document生成和解析XML操作

    這篇文章主要介紹了Java Document生成和解析XML操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java正則表達(dá)式易錯知識點匯總

    Java正則表達(dá)式易錯知識點匯總

    這篇文章主要總結(jié)Java正則表達(dá)式易錯知識,對易錯知識點進(jìn)行分類整理,幫助大家更好的學(xué)習(xí)Java正則表達(dá)式,感興趣的小伙伴們可以參考一下
    2015-12-12
  • java 類加載機制和反射詳解及實例代碼

    java 類加載機制和反射詳解及實例代碼

    這篇文章主要介紹了java 類加載機制和反射詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • Springboot多環(huán)境開發(fā)及使用方法

    Springboot多環(huán)境開發(fā)及使用方法

    這篇文章主要介紹了Springboot多環(huán)境開發(fā)及多環(huán)境設(shè)置使用、多環(huán)境分組管理的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-03-03
  • Eclipse中創(chuàng)建Web項目最新方法(2023年)

    Eclipse中創(chuàng)建Web項目最新方法(2023年)

    在Java開發(fā)人員中,最常用的開發(fā)工具應(yīng)該就是Eclipse,下面這篇文章主要給大家介紹了關(guān)于Eclipse中創(chuàng)建Web項目2023年最新的方法,需要的朋友可以參考下
    2023-09-09
  • Java基礎(chǔ)之static的用法

    Java基礎(chǔ)之static的用法

    這篇文章主要介紹了Java基礎(chǔ)之static的用法,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有很大的幫助,需要的朋友可以參考下
    2021-05-05
  • Java項目打包發(fā)布到maven私倉常見的幾種方式

    Java項目打包發(fā)布到maven私倉常見的幾種方式

    這篇文章主要介紹了項目打包發(fā)布到maven私倉常見的幾種方式,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下
    2021-03-03
  • java之構(gòu)造器的重載問題

    java之構(gòu)造器的重載問題

    這篇文章主要介紹了java之構(gòu)造器的重載問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03

最新評論