最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Seata分布式事務(wù)出現(xiàn)ABA問(wèn)題解決

 更新時(shí)間:2022年11月09日 16:26:13   作者:夢(mèng)想實(shí)現(xiàn)家_Z  
這篇文章主要為大家介紹了Seata分布式事務(wù)出現(xiàn)ABA問(wèn)題解決方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

兄弟們,最近處理了一個(gè)seata的issue,關(guān)于seata分布式事務(wù)長(zhǎng)期回滾失敗后,突然回滾成功了:

這個(gè)問(wèn)題的出現(xiàn)需要以下兩個(gè)契機(jī):

  • 在執(zhí)行分布式事務(wù)期間,有本地事務(wù)與分布式事務(wù)操作同一張表中的數(shù)據(jù)導(dǎo)致臟寫(xiě)產(chǎn)生;
  • 在回滾時(shí),seata對(duì)比afterImage與當(dāng)前數(shù)據(jù)不一致,導(dǎo)致回滾失敗,此時(shí)會(huì)一直重試;
  • 當(dāng)手工校準(zhǔn)數(shù)據(jù)后,某一時(shí)刻afterImage與當(dāng)前數(shù)據(jù)一致,此時(shí)回滾重試成功,ABA問(wèn)題產(chǎn)生;

從源碼中定位原因

為了避免ABA問(wèn)題的產(chǎn)生,通過(guò)與seata社區(qū)的大佬討論,最終決定在回滾時(shí),如果對(duì)比afterImage與當(dāng)前數(shù)據(jù)不一致的情況下,不再嘗試回滾重試。這樣的話,即使后續(xù)通過(guò)人工校準(zhǔn)后,也不會(huì)回滾了。但是這樣有另一個(gè)問(wèn)題,就是人工校準(zhǔn)后,這個(gè)分布式事務(wù)就一直遺留在數(shù)據(jù)庫(kù)中無(wú)法刪除了。針對(duì)這個(gè)問(wèn)題,seata應(yīng)該要提供一個(gè)restful api讓開(kāi)發(fā)人員在數(shù)據(jù)校準(zhǔn)后能夠刪除掉對(duì)應(yīng)的分布式事務(wù)數(shù)據(jù)。

在seata源碼中,如果校驗(yàn)afterImage與當(dāng)前數(shù)據(jù)不一致后,會(huì)拋出SQLException,最終會(huì)被上層代碼捕獲包裝成BranchTransactionException異常,但是里面的code屬性是BranchRollbackFailed_Retriable,這也是導(dǎo)致seata一直重試回滾的根本原因:

Result<Boolean> afterEqualsCurrentResult = DataCompareUtils.isRecordsEquals(afterRecords, currentRecords);
        if (!afterEqualsCurrentResult.getResult()) {
            // 先比較afterImage與當(dāng)前數(shù)據(jù),如果不一致,那么再比較當(dāng)前數(shù)據(jù)和beforeImage是否一致
            Result<Boolean> beforeEqualsCurrentResult = DataCompareUtils.isRecordsEquals(beforeRecords, currentRecords);
            // 如果當(dāng)前數(shù)據(jù)和beforeImage一致,那么不需要回滾了,因?yàn)橄喈?dāng)于已經(jīng)回滾了
            if (beforeEqualsCurrentResult.getResult()) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Stop rollback because there is no data change " +
                            "between the before data snapshot and the current data snapshot.");
                }
                // no need continue undo.
                return false;
            } else {
                // 否則,直接拋出SQLException,并告知undo log臟寫(xiě)了
                if (LOGGER.isInfoEnabled()) {
                    if (StringUtils.isNotBlank(afterEqualsCurrentResult.getErrMsg())) {
                        LOGGER.info(afterEqualsCurrentResult.getErrMsg(), afterEqualsCurrentResult.getErrMsgParams());
                    }
                }
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("check dirty data failed, old and new data are not equal, " +
                            "tableName:[" + sqlUndoLog.getTableName() + "]," +
                            "oldRows:[" + JSON.toJSONString(afterRecords.getRows()) + "]," +
                            "newRows:[" + JSON.toJSONString(currentRecords.getRows()) + "].");
                }
                throw new SQLException("Has dirty records when undo.");
            }
        }

在上層調(diào)用代碼中,我們可以找到這樣一段:

catch (Throwable e) {
    if (conn != null) {
        try {
            conn.rollback();
        } catch (SQLException rollbackEx) {
            LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx);
        }
    }
    // 包裝異常
    throw new BranchTransactionException(BranchRollbackFailed_Retriable, String
                    .format("Branch session rollback failed and try again later xid = %s branchId = %s %s", xid,
                        branchId, e.getMessage()), e);
}

根據(jù)源碼分析,我們發(fā)現(xiàn)在數(shù)據(jù)校驗(yàn)后拋出的SQLException會(huì)被包裝成code屬性為BranchRollbackFailed_RetriableBranchTransactionException異常,這樣會(huì)導(dǎo)致seata不斷重試回滾操作。

如何處理

我們需要將這個(gè)SQLException調(diào)整為一個(gè)更加具體的異常,比如SQLUndoDirtyException這種能夠明確地表示undo log被臟寫(xiě)的異常,另外我們?cè)谏蠈哟a中同樣需要針對(duì)SQLUndoDirtyException做特殊處理,比如包裝成new BranchTransactionException(BranchRollbackFailed_Unretriable)不可重試的狀態(tài)。

先創(chuàng)建自定義的異常:SQLUndoDirtyException

import java.io.Serializable;
import java.sql.SQLException;
/**
 * @author zouwei
 */
class SQLUndoDirtyException extends SQLException implements Serializable {
    private static final long serialVersionUID = -5168905669539637570L;
    SQLUndoDirtyException(String reason) {
        super(reason);
    }
}

調(diào)整SQLExceptionSQLUndoDirtyException:

Result<Boolean> afterEqualsCurrentResult = DataCompareUtils.isRecordsEquals(afterRecords, currentRecords);
        if (!afterEqualsCurrentResult.getResult()) {
            // 先比較afterImage與當(dāng)前數(shù)據(jù),如果不一致,那么再比較當(dāng)前數(shù)據(jù)和beforeImage是否一致
            Result<Boolean> beforeEqualsCurrentResult = DataCompareUtils.isRecordsEquals(beforeRecords, currentRecords);
            // 如果當(dāng)前數(shù)據(jù)和beforeImage一致,那么不需要回滾了,因?yàn)橄喈?dāng)于已經(jīng)回滾了
            if (beforeEqualsCurrentResult.getResult()) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Stop rollback because there is no data change " +
                            "between the before data snapshot and the current data snapshot.");
                }
                // no need continue undo.
                return false;
            } else {
                // 否則,直接拋出SQLException,并告知undo log臟寫(xiě)了
                if (LOGGER.isInfoEnabled()) {
                    if (StringUtils.isNotBlank(afterEqualsCurrentResult.getErrMsg())) {
                        LOGGER.info(afterEqualsCurrentResult.getErrMsg(), afterEqualsCurrentResult.getErrMsgParams());
                    }
                }
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("check dirty data failed, old and new data are not equal, " +
                            "tableName:[" + sqlUndoLog.getTableName() + "]," +
                            "oldRows:[" + JSON.toJSONString(afterRecords.getRows()) + "]," +
                            "newRows:[" + JSON.toJSONString(currentRecords.getRows()) + "].");
                }
                // 替換為具體的SQLUndoDirtyException異常
                throw new SQLUndoDirtyException("Has dirty records when undo.");
            }
        }

這樣的話,我們?cè)谏蠈哟a中,就可以針對(duì)性地處理了:

catch (Throwable e) {
    if (conn != null) {
        try {
            conn.rollback();
        } catch (SQLException rollbackEx) {
            LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx);
        }
     }
     // 如果捕捉的異常為SQLUndoDirtyException,那么包裝為BranchRollbackFailed_Unretriable
     if (e instanceof SQLUndoDirtyException) {
         throw new BranchTransactionException(BranchRollbackFailed_Unretriable, String.format(
                        "Branch session rollback failed because of dirty undo log, please delete the relevant undolog after manually calibrating the data. xid = %s branchId = %s",
                        xid, branchId), e);
      }
      throw new BranchTransactionException(BranchRollbackFailed_Retriable,
                    String.format("Branch session rollback failed and try again later xid = %s branchId = %s %s", xid,
                        branchId, e.getMessage()),
                    e);
}

我們?cè)谏蠈诱{(diào)用代碼中捕捉指定的SQLUndoDirtyException,直接包裝為BranchRollbackFailed_Unretriable狀態(tài)的BranchTransactionException,這樣我們的分布式事務(wù)就不會(huì)一直重試回滾操作了。

下一步就需要開(kāi)發(fā)人員人工介入校準(zhǔn)數(shù)據(jù)后刪除對(duì)應(yīng)的undo log,在一系列操作處理完畢后,另外還需要seata tc端提供對(duì)應(yīng)的restful api開(kāi)放對(duì)應(yīng)的手工觸發(fā)回滾的操作,以便保證校準(zhǔn)后的分布式事務(wù)正常結(jié)束。

小結(jié)

我們根據(jù)seata使用人員反饋的問(wèn)題,通過(guò)源碼分析找到了造成問(wèn)題的原因:

  • 開(kāi)發(fā)人員在使用seata的時(shí)候,對(duì)于同一張表的操作沒(méi)有使用@GlobalTransactional注解覆蓋到,導(dǎo)致了undo log被臟寫(xiě);
  • 當(dāng)產(chǎn)生回滾時(shí),在進(jìn)行數(shù)據(jù)校驗(yàn)時(shí),發(fā)現(xiàn)afterImage與當(dāng)前數(shù)據(jù)不一致進(jìn)而無(wú)法正常回滾,拋出SQLException,最終包裝成BranchRollbackFailed_Retriable異常,導(dǎo)致seata一直重試回滾;
  • 在數(shù)據(jù)校準(zhǔn)后,某一刻的數(shù)據(jù)與afterImage一致,此時(shí)seata就回滾成功,形成ABA問(wèn)題;

該pr將在1.6版本后解決seata分布式事務(wù)一直嘗試回滾的問(wèn)題,可以避免ABA問(wèn)題的產(chǎn)生,后續(xù)還需要提供一些其他功能輔助開(kāi)發(fā)人員回滾數(shù)據(jù)。

以上就是Seata分布式事務(wù)出現(xiàn)ABA問(wèn)題解決的詳細(xì)內(nèi)容,更多關(guān)于Seata分布式事務(wù)ABA的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

正阳县| 恩平市| 吉水县| 垦利县| 平远县| 金沙县| 湖南省| 鹤庆县| 衡东县| 平乐县| 南宁市| 华蓥市| 漳浦县| 晋中市| 儋州市| 利辛县| 高阳县| 攀枝花市| 丰原市| 关岭| 阿拉善右旗| 安阳市| 唐河县| 四平市| 吉林省| 北辰区| 禹州市| 武威市| 汝城县| 厦门市| 金堂县| 五家渠市| 安多县| 来凤县| 内黄县| 江安县| 静安区| 仙桃市| 叙永县| 普定县| 长武县|