seata tcc模式下的一個性能問題

  • 2020 年 1 月 13 日
  • 筆記

本文解釋Seata中,AT模式和MT模式下的一個一階段的區別。

根據兩階段行為模式的不同,Seata將分支事務劃分為2種:

  • Automatic (Branch) Transaction Mode
  • Manual (Branch) Transaction Mode

1.AT模式

AT 模式基於 支援本地 ACID 事務 的 關係型資料庫:

一階段 prepare 行為:在本地事務中,一併提交業務數據更新和相應回滾日誌記錄。 二階段 commit 行為:馬上成功結束,自動 非同步批量清理回滾日誌。 二階段 rollback 行為:通過回滾日誌,自動 生成補償操作,完成數據回滾。

2. MT模式

相應的,MT 模式,不依賴於底層數據資源的事務支援: 一階段 prepare 行為:調用 自定義 的 prepare 邏輯。 二階段 commit 行為:調用 自定義 的 commit 邏輯。 二階段 rollback 行為:調用 自定義 的 rollback 邏輯。 所謂 MT 模式,是指支援把 自定義 的分支事務納入到全局事務的管理中。

3.一階段解讀

在AT模式下,一階段會做如下幾個操作:

1.解析業務sql; 2.獲取sql執行前的鏡像,前鏡像; 3.執行業務sql; 4.獲取sql執行後的鏡像,後鏡像; 5.添加undo_log日誌,把前後鏡像數據和業務sql相關的資訊組成回滾日誌,添加到undo_log中; 6.向TC註冊分支事務,並申請相關目標數據的全局鎖; 7.事務提交,將業務操作和undo_log一起提交; 8.上報分支事務提交結果給TC; 9.釋放本地鎖; 10.釋放資料庫連接;

在AT模式下,一階段,會有如上的多個步驟,以及解析存儲undo_log等操作;那麼,在MT模式中,由於prepare邏輯有對應的rollback邏輯,顯然這裡是不用再添加回滾資訊的。那麼,這MT模式下一階段的處理邏輯,是如何避免上述操作帶來的性能損耗呢?

4.源碼分析

在方法攔截器MethodInterceptor介面下,有一個TCC攔截器實現類TccActionInterceptor,這個實現類有一個invoke方法:

   @Override      public Object invoke(final MethodInvocation invocation) throws Throwable {          if(!RootContext.inGlobalTransaction()){              //not in transaction              return invocation.proceed();          }          Method method = getActionInterfaceMethod(invocation);          TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);          //try method          if (businessAction != null) {              //save the xid              String xid = RootContext.getXID();              //clear the context              RootContext.unbind();              try {                  Object[] methodArgs = invocation.getArguments();                  //Handler the TCC Aspect                  Map<String, Object> ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,                          new Callback<Object>() {                              @Override                              public Object execute() throws Throwable {                                  return invocation.proceed();                              }                          });                  //return the final result                  return ret.get(Constants.TCC_METHOD_RESULT);              } finally {                  //recovery the context                  RootContext.bind(xid);              }          }          return invocation.proceed();      }

可以看到,在切面的切入點執行之前,和之後,有2個關鍵操作: 把xid解綁

//save the xid  String xid = RootContext.getXID();  //clear the context  RootContext.unbind();

恢復xid綁定

//recovery the context  RootContext.bind(xid);

這麼做的目的是什麼呢?

當把xid解綁後,tcc的這個prepare分支事務執行時,框架不會攔截業務sql進行解析,也不會存儲前後鏡像和生成undo_log日誌,(即使使用了代理數據源,也不會)這樣,tcc模式下,就避免了一階段的上述操作帶來的損耗。