天天看點

seata源碼解析:seata是如何支援TCC模式的?

seata源碼解析:seata是如何支援TCC模式的?

最常應用的模式

TCC模式應該是企業應用最廣的一種模式,主要分為2個階段

  1. prepare,鎖定相關的資源,保證事務的隔離性
  2. commit/rollback,根據全局事務的執行狀态來執行分支事務的送出和復原

TCC模式不需要進行資料源代理,因為送出和復原操作在業務層面都已經定義好了,不需要通過資料源代理生成對應的復原操作

當然事務的執行狀态還是會通過seata server記錄在global_table和branch_table表中

通過TccActionInterceptor對方法進行增強

當使用TCC模式時,我們需要在prepare方法上@TwoPhaseBusinessAction,表明這是一個分支事務,并通過@TwoPhaseBusinessAction的commitMethod屬性和rollbackMethod屬性指明這個分支事務對應的送出操作和復原操作。

是以當執行被@TwoPhaseBusinessAction标注的方法時,會執行到TccActionInterceptor#invoke方法,增強邏輯交給ActionInterceptorHandler#proceed來處理

// ActionInterceptorHandler
public Map<String, Object> proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,
                                   Callback<Object> targetCallback) throws Throwable {
    Map<String, Object> ret = new HashMap<>(4);

    //TCC name
    String actionName = businessAction.name();
    BusinessActionContext actionContext = new BusinessActionContext();
    actionContext.setXid(xid);
    //set action name
    actionContext.setActionName(actionName);

    //Creating Branch Record
    // 注冊分支事務
    String branchId = doTccActionLogStore(method, arguments, businessAction, actionContext);
    actionContext.setBranchId(branchId);
    //MDC put branchId
    MDC.put(RootContext.MDC_KEY_BRANCH_ID, branchId);

    //set the parameter whose type is BusinessActionContext
    // 如果被代理的方法中有BusinessActionContext類型,則把actionContext設定進去
    // 這樣方法執行的時候就能拿到actionContext了
    Class<?>[] types = method.getParameterTypes();
    int argIndex = 0;
    for (Class<?> cls : types) {
        if (cls.getName().equals(BusinessActionContext.class.getName())) {
            arguments[argIndex] = actionContext;
            break;
        }
        argIndex++;
    }
    //the final parameters of the try method
    ret.put(Constants.TCC_METHOD_ARGUMENTS, arguments);
    //the final result
    // 執行被代理方法,并設定結果
    ret.put(Constants.TCC_METHOD_RESULT, targetCallback.execute());
    return ret;
}      

proceed方法的主要邏輯為

  1. 建構BusinessActionContext,并注冊分支事務
  2. 如果prepare階段的方法入參有BusinessActionContext,則把對應的值設定進去(這就是我們在調用prepare階段的方法時,傳入的BusinessActionContext為null,但實際執行時并不為null的原因)
protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction,
                                     BusinessActionContext actionContext) {
    String actionName = actionContext.getActionName();
    String xid = actionContext.getXid();
    // 将方法中用@BusinessActionContextParameter修飾的入參名字即值,轉成map傳回
    Map<String, Object> context = fetchActionRequestContext(method, arguments);
    // 設定開始時間
    context.put(Constants.ACTION_START_TIME, System.currentTimeMillis());

    //init business context
    // 往context設定commit和rollback的方法名
    initBusinessContext(context, method, businessAction);
    //Init running environment context
    // 往context設定本機ip位址
    initFrameworkContext(context);
    actionContext.setActionContext(context);

    //init applicationData
    Map<String, Object> applicationContext = new HashMap<>(4);
    applicationContext.put(Constants.TCC_ACTION_CONTEXT, context);
    String applicationContextStr = JSON.toJSONString(applicationContext);
    try {
        //registry branch record
        // 向tc注冊分支事務
        Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,
            applicationContextStr, null);
        return String.valueOf(branchId);
    } catch (Throwable t) {
        String msg = String.format("TCC branch Register error, xid: %s", xid);
        LOGGER.error(msg, t);
        throw new FrameworkException(t, msg);
    }
}      

actionContext主要用來存儲動作上下文的一些參數(我們在二階段復原或者送出的時候,用來建構方法中的BusinessActionContext參數用),以我們之前的例子為例,最終建構的actionContext如下,将actionContext存儲在branch_table表中application_data字段

"{""actionContext"":{""action-start-time"":1633261303972,""money"":200,""sys::prepare"":""prepare"",""fromUserId"":""1001"",""sys::rollback"":""cancel"",""sys::commit"":""commit"",""host-name"":""192.168.97.57"",""toUserId"":""1002"",""actionName"":""prepare""}}"      
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8;      

在TCC模式下,分支事務的注冊功能是由代理對象完成的,是以不能通過自調用的方式來調用prepare方法,不然會造成事務失效(和spring事務失效的原因一樣哈)。

參考部落格