XA式、非XA式Spring分散式事務的實現

ImportNew發表於2015-04-25

Spring應用的幾種事務處理機制

Java Transaction API和XA協議是Spring常用的分散式事務機制,不過你可以選擇選擇其他的實現方式。理想的實現取決於你的應用程式使用何種資源,你願意在效能、安全、系統穩健性、資料完整方面做出何種權衡。在這次JavaWorld大會上,來自SpringSource的David Syer跟大家分享了Spring應用的幾種事務處理機制、三種XA式、四種非XA式事務協議。

Spring框架支援Java Transaction API(JTA),這樣應用就可以脫離Java EE容器,轉而利用分散式事務以及XA協議。然而即使有這樣的支援,XA開銷是昂貴的,不穩定而且笨重不利於管理,不過一些其他的應用可以避免使用XA協議。

為了讓大家對所涉及的幾種分散式事務有所瞭解,我會分析七種事務處理模式,並 給出具體程式碼實現。並且從安全或者穩定性入手倒序展示,可以看看從安全、穩定性出發,如何在一般場景下,保障資料高完整性和原子性。當然隨著話題的深入, 更多的說明以及限制就會出現。模式也可以從執行時開銷倒序展示。考慮到所有模式都是結構化或者學術性的,這一點有別於業務模型,因此我不打算展開業務用例 分析,僅僅關注每種模式其少部分程式碼如何工作的。

儘管只有起初的三種模式涉及到 XA協議,不過從效能角度出發,這些模式或許無法滿足需求。考慮到這些模式無處不在,我不想做過多地擴充套件,只是對第一種模式做一個簡單的展示。讀完此文,你可以瞭解可以用分散式事務做些什麼、不能做什麼以及如何、何時避免使用XA,何時必須使用。

分散式事務以及原子性

分散式事務涉及不止一個事務資源。比如,在關聯式資料庫和訊息中介軟體之間通訊的聯結器,通常這些資源擁有類似begin()、rollback()、commit()的API。在此,一個事務資源通常是一個工廠產品,這個工廠通常由底層平臺提供:以資料庫為例,DataSource提供Connection,或者Java Persistence API(JPA)的EntityManager介面;又如Java Message Service(JMS)提供的Session。

一個典型的例子,一個JMS訊息觸發一次資料庫更新。此過程可以分解成一時間線,一個成功的互動順序是下面這樣:

  1. 開啟訊息事務
  2. 接受訊息
  3. 開啟資料庫事務
  4. 更新資料庫
  5. 提交資料庫事務
  6. 提交訊息事務

如果資料庫出錯,比如更新時出現諸如違反約束的問題,一個理想的順序應該是下面這個樣子:

  1. 開啟訊息事務
  2. 接受訊息
  3. 開啟資料庫事務
  4. 更新資料庫失敗
  5. 回滾資料庫事務
  6. 回滾訊息事務

在這個案例中,最後的回滾發生後訊息返回給中介軟體,並且在某種程度返回的訊息會被其他事務所接收。通常這是件好事,可能你並沒有對失敗做記錄。自動重試處理異常機制超出了本文的範疇。

以上兩種時間線中最重要的特性是它們的原子性,形成一個單一的邏輯事務單元,要麼都成功要麼都失敗。

那麼用什麼確保時間線會的順序呢?事務資源之間必須保持某種同步,一旦對某個資料來源做提交,要麼都提交了,要麼都回滾。否者整個事務就不缺乏原子性。之所以是分散式事務,是因為有多個資料來源,沒有同步就沒有原子性。分散式事務技術和概念的核心問題都是圍繞資源的同步或者無法同步展開的。

前三種模式的以下討論都是基於XA協議,考慮到這三種模式分佈廣泛,本文不會涉及太多的細節,倘若你熟悉XA模式或許願意直接跳到共享事務資源模式。

二階段提交完整XA協議

如果你需要近乎完美的防護 (close-to-bulletproof)確保你的應用事務在斷電後恢復以及伺服器崩潰,完整XA是不二之選。共享資源通常需要做事務同步,在此情況下,它是一個採用XA協議協調處理過程的資訊特殊的事務管理器。在Java領域,從開發者的角度看,這個協議是通過JPA UserTransaction暴露給大家。

基於系統介面,XA作為一種促成科技(enabling technology)對多數開發人員不可見,因此他們需要知道XA在哪、促成什麼、耗損如何以及如何利用事務資源。事務管理器採用二階段提交(2PC)協議,在確保事務結束前所有資源採用同一個事務結果的同時,也會帶來效能耗損。

如 果是Spring促成的(Spring-enabled),應用會採用Spring的JtaTransactionManager以及Spring宣告式 事務管理,這樣會隱藏到了底層事務同步的具體細節。對於開發人員用沒用XA的差別就在於對工廠資源的配置:DataSource例項,以及應用的事務管理 器。本文會通過一個應用案例(atomikos-db專案)來揭示這個配置,資料庫例項和事務管理器僅是XA或者JTA特定的應用元素。

為了揭示此案例如何工作,在com.springsource.open.db.下執行這個單元測試。一個簡單的 MulipleDataSourceTests類僅是將資料插入兩個資料來源中,並且採用Spring整合支援的特性對事務進行回滾,程式碼見清單1:

清單1、事務回滾

@Transactional
  @Test
  public void testInsertIntoTwoDataSources() throws Exception {

    int count = getJdbcTemplate().update(
        "INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", 0,
        "foo");
    assertEquals(1, count);

    count = getOtherJdbcTemplate()
        .update(
            "INSERT into T_AUDITS (id,operation,name,audit_date) values (?,?,?,?)",
            0, "INSERT", "foo", new Date());
    assertEquals(1, count);

    // Changes will roll back after this method exits

  }

接著驗證這兩個操作是否同時回滾,程式碼清單如清單2:

清單2、回滾驗證

@AfterTransaction
  public void checkPostConditions() {

    int count = getJdbcTemplate().queryForInt("select count(*) from T_FOOS");
    // This change was rolled back by the test framework
    assertEquals(0, count);

    count = getOtherJdbcTemplate().queryForInt("select count(*) from T_AUDITS");
    // This rolled back as well because of the XA
    assertEquals(0, count);

  }

更進一步理解Spring事務管理如何工作以及如何配置,請參看Spring參考指南

一階段提交優化XA協議

許多事務管理器採用這種優化模式,可以避免單一事務資源下的2PC過度開銷,你的應用伺服器最好能夠判別此種情況。

協議和最終資源策略

多數XA事務管理器另一個特性是,不論是單一XA兼 容資源還是所有資源都XA相容,事務管理器均能提供相同的恢復保障。它們是通過給資源排序,並且給非XA資源投票實現,倘若事務提交失敗,所有其他的資源 都能回滾。事務有近乎百分百的保障,但缺點是,倘若事務失敗,此時不會留下太多資訊。換言之,如果要獲取這些資訊,需要做一些額外的步驟,比如在一些高階實現。

共享事務資源模式

這個模式不錯,系統所有的事務資源由一個相同的資源提供支援進而移除XA,降低系統的複雜度,提高吞吐量。當然不能拿來處理所有的用例,但卻是如XA般堅固,而且處理速度更加的快。共享事務資源模式作為一種保障存在與特定的平臺和處理場景中。

一個簡單熟悉的例子就是共享一個資料庫的Connection,它存在於一個物件關係模型(ORM)控制元件和一個JDBC控制元件之間。Spring事務管理器就是如此,它支援ORM工具,比如Hibernate、EclipseLink以及Java Persistence API(JPA)。相同的事務能安全的跨越ORM和JDBC控制元件之間,通常此事務是由service層受事務控制的執行方法所驅動的。

此模式的另外一個特點是,訊息驅動的單個資料庫更新,如本文初始階段的簡單例子。消 息中介軟體系統需要儲存這些資料,通常是關係型資料庫。實現這種模式,需要將訊息系統指定到相同的用於儲存業務資料的資料庫中。這種模式依賴訊息中介軟體供應 商所提供的儲存策略細節,以便能夠將訊息中介軟體配置在相同的資料庫中,並嵌入相同的事務處理。

不是所有的供應商都提供了此種模式,不過一種可替代,幾乎可以用於任何資料庫的方式,即利用Apache ActiveMQ的傳遞訊息,並且插入一個儲存策略進入訊息代理中。一旦你知道了其中的訣竅,配置起來很容易的,我會在本文的shared-jms-db 專案案例中演示。此模式的所用的程式碼無需關注,它們會在Spring配置中得到宣告。

名為 SynchronousMessageTriggerAndRollbackTests 的案例中,單元測試校驗所有與同步訊息接收者相關的訊息。 testReceiveMessageUpdateDatabase方法接受兩個訊息,接著利用訊息插入兩條記錄到資料庫中。如果此方法退出,測試框架回 滾事務,這樣就能校驗訊息和資料庫更新是否發生回滾,如清單3所示:

清單3、驗證訊息和資料庫更新是否回滾

@AfterTransaction
public void checkPostConditions() {

  assertEquals(0, SimpleJdbcTestUtils.countRowsInTable(jdbcTemplate, "T_FOOS"));
  List<String> list = getMessages();
  assertEquals(2, list.size());

}

配置檔案最重要的部分就是ActiveMQ的持久化策略,連線訊息系統和相同資料來源作為業務資料,Spring JmsTemplate的flag標籤用來接收訊息。清單4 展示瞭如何配置ActiveMQ持久化策略:

清單4、ActiveMQ持久化配置

<bean id="connectionFactory" depends-on="brokerService">
      <property name="brokerURL" value="vm://localhost?async=false" />
</bean>

<bean id="brokerService" init-method="start" destroy-method="stop">
       <property name="persistenceAdapter">
          <bean>
            <property name="dataSource">
                 <bean>
                      <property name="targetDataSource" ref="dataSource"/>
                      <property name="jmsTemplate" ref="jmsTemplate"/>
                 </bean>
            </property>
            <property name="createTablesOnStartup" value="true" />
          </bean>
       </property>
</bean>

清單5展示了Spring JmsTemplate中用來接收訊息的flag標籤:

清單5、設定JmsTemplate事務應用

<bean id="jmsTemplate">
        <!-- This is important... -->
        <property name="sessionTransacted" value="true" />
</bean>

若 sessionTransacted不為true,JMS session transaction API就無法被呼叫,訊息接受者將無法回滾。最為重要的是,嵌入的代理包含一個特殊的async=false引數以及DataSource外包類,這樣就可以確保ActiveMQ擁有同Spring一樣的事務JDBC Connection。

一個共享資料庫源可以由獨立的單個資料來源組成,特別是這些資料來源擁有同樣的RDBMS平臺。企業級數 據庫供應商均提供同名概念(the notion of synonyms)支援,表可以作為一個同名(synonyms)宣告於多個schema中。藉助這個手段,分佈在不同物理平臺上的資料,可以均可 JDBC client同意Connection事務管理。比如在一個真實的系統中,採用ActiveMQ共享資源模式實現,通常需要為訊息和業務資料建立同名。

效能和JDBCPersistenceAdapter

ActiveMQ 社群的一些開發人員表示JDBCPersistenceAdapter會引發效能問題。然而,許多專案和上線系統採用ActiveMQ與關係型資料庫搭配使 用。在這些案例中,公認的是日誌版本的介面卡可以用來提供效能,當然這對共享資源模式來說是不利的,因為日誌本身就是一個新的事務資源。然而,對於 JDBCPersistenceAdapter大家眾說紛紜,看法不一。確實,有理由相信採用共享資源或許能提高日誌案例的效能。這個一結論來自 Sping以及AtiveMQ工程師團隊的研究。

非訊息場景(多個資料庫)的另一種共享資源技術就是,在RDBMS平臺一級利用Oracle資料庫連結一個特徵到兩個資料庫schema中。或許這需要改變應用程式碼,或者建立同名,因為表的別名會指向一個已連結資料庫,此資料庫包含連結的名稱。

最大努力一次提交模式

開發人員必須知道,最大努力一次提交模式應用相當的普遍,但是在一些場景並不適用。這是一種非XA模式,它包含一 個同步大量資源單一相提交(single-phase commit)。參與者應該意識到這種折中,如果不用兩階段提交,那最大努力一次提交模式的安全性不如XA事務但也是相當不錯。許多海量資料、大吞吐量事 務處理系統用最大努力一次提交模式提高效能。

最基本的理念就是在單一事務中儘可能的延遲所有資源的提交,這樣唯一可能發生錯誤的就是基礎元件,而非業務處理錯誤。 採用最大努力一次提交模式的系統假定基礎元件出錯的可能性非常小,因此能夠承受風險獲得較高的吞吐量收益。如果業務處理服務也被設計為一個幕等式 (idempotent),發生錯誤的可能性也很小。(譯者注:幕等式是數學和電腦科學特定運算的一個特性,應用初始化以後多次操作其結果都不會再發生改變)

為了幫助大家更好的理解這個模式分析失敗結果,我會用訊息驅動資料庫更新作為例子來加以說明。

本事務中兩個資源將被統計、並且計算。訊息事務在一個資料庫之前開啟,然後逆序結束。成功的順序或許和本文開始的時候一模一樣:

  1. 開啟訊息事務
  2. 接受訊息
  3. 開啟資料庫事務
  4. 更新資料庫
  5. 提交資料庫事務
  6. 提交訊息事務

準確的說,此順序前四個步驟都不重要,重要的是訊息必須在資料庫更新之前被接受,並且每個事務必須在對應的資源被呼叫之前開啟,因此合理的順序應該如下:

  1. 開啟訊息事務
  2. 開啟資料庫事務
  3. 接受訊息
  4. 更新資料庫
  5. 提交資料庫事務
  6. 提交訊息事務

關鍵點是最後兩步很重要,它們必須放在最後按順序執行。按序之所以重要,其本身就是一個技術問題,不過這個順序是有業務需 要決定的。這個順序告訴開發者,其中的一個事務資源是特殊的,這個資源包含了如何在其他資源上運作的指令。這是一個業務順序:系統不能自動告知走事務到哪一步了。即使訊息和資料庫是兩個資源,事務也常常遵循這一流程。按序之所以重要,是因為事務必須處理失敗案例。迄今最常見的失敗案例是,諸如壞資料、程式設計錯誤等失敗的業務處理。本例中,這兩個事務很容易用來相應一個異常並且回滾。這樣,業務資料的完成性得到保障,時間線與本文開始列出的理想失敗案例神似。

引發回滾機制的精確性不是很重要的,這樣的機制有好一些。最重要的是,提交或者回滾必須按照資源中業務順序的逆序發生。在一個應用案例中,訊息事務必須在最後提交,因為處理業務的指令包含在這個資源中,這是因為,很少有失敗案例 其第一次提交成功,第二次失敗。在這個點上,所有設計業務處理的部分都已完成,那唯一能引起部分失敗的因素,可能訊息中介軟體的基礎問題。

注意如果 資料庫資源提交失敗,那麼事務最終會發生回滾。所以是說非原子性失敗模型(failure mode)其第一個事務會提交,第二個事務發生回滾。通常,事務中有n個資源,那麼就存在n-1個這樣的失敗模型,這會導致一個子事務回滾後,其它一些資源處在提交後的不一致狀態。在訊息資料庫用例中,失敗模型的結局是,訊息會回滾,然後是其他的事務,即使其他這些事務都經成功處理;可以斷定最糟糕的事情 就是重複訊息(duplicate message)被傳遞過來。什麼是重複訊息呢?通常情況下,事務中的早期資源被認為是包含有後續資源處理流程的訊息,因此失敗模型的結果可以被認為就是重複訊息。

一 些富有冒險精神的人認為重複訊息發生的可能性微乎其微,因此懶得去預測這些訊息。然而,為了更加確信業務資料是準確性和一致性,還是需要在業務邏輯層面對 此有清晰地認識。 如果懷疑重複訊息可能發生,那麼必須核實,業務處理過程是否處理過資料,在處理資料之前是否什麼都沒做。這個特定的說明有時指幕等業務服務模型 (Idempotent Business Service pattern)。

相關案例包括兩個採用此模型的同步事務資源例子,我會在後面做一一分析,以及一些其它選項。

Spring和訊息驅動POJO

案例best-jms-db專案中的程式碼,開發人員採用主流配置,這樣就可以使用最大努力一次提交模式。具體的做法是這樣的,通過一個非同步的監聽器將訊息傳給一個佇列,並將此資料插入 資料庫表中。

TransactionAwareConnectionFactoryProxy 是Spring的一個儲存控制元件,應用於這個模式中,也是最關鍵的組成部分。放棄採用供應商提供的粗顆粒度的 ConnectionFactory,configuration採用裝飾模式包裝了一個ConnectionFactory,用它來處理事務同步問題。 具體配置見jms-context.xml,如下清單6所示:

清單6、配置一個TransactionAwareConnectionFactoryProxy去包裝一個供應商提供的JMS ConnectionFactory

<bean id="connectionFactory">
        <property name="targetConnectionFactory">
            <bean depends-on="brokerService">
                <property name="brokerURL" value="vm://localhost"/>
            </bean>
        </property>
        <property name="synchedLocalTransactionAllowed" value="true" />
</bean>

ConnectionFactory 無需知道哪個事務管理器與其同步,每一時刻僅有一個事務處在活動(active)狀態。這些是由Spring內部在處理。事務驅動是由配置在data- source-context.xml中的DataSourceTransactionManager完成的,事務管理器必須由輪詢和接受訊息的JMS監聽器容器監控。

<jms:listener-container transaction-manager="transactionManager" >
    <jms:listener destination="async" ref="fooHandler" method="handle"/>
</jms:listener-container>

fooHandler和方法會告知監聽器容器某個具體的控制元件的具體方法得到呼叫,當一個訊息達到”非同步”佇列。handler是如此實現的,接受一個String作為引數訊息,並將其作為資料插入記錄中。

public void handle(String msg) {

  jdbcTemplate.update(
      "INSERT INTO T_FOOS (ID, name, foo_date) values (?, ?,?)", count.getAndIncrement(), msg, new Date());

}

為了模擬失敗,程式碼用一個FailureSimulator切面,它會檢查訊息內容是否真的失敗;如清單7所示,在FooHandler在事務結束之前,處理訊息之後,maybeFail()方法得到呼叫,所以它能影響事務的結果。

清單7、maybeFail()方法

@AfterReturning("execution( ..*Handler+.handle(String)) && args(msg)")
public void maybeFail(String msg) {
         if (msg.contains("fail")) {
               if (msg.contains("partial")) {
                      simulateMessageSystemFailure();
               } else { 
                 simulateBusinessProcessingFailure();
               }
         }
}

simulateBusinessProcessingFailure() 方法會丟擲DataAccessException,就像資料庫訪問真的失敗。一旦這個方法被呼叫,最理想的結局是所有的資料庫以及訊息事務都能回滾。 這個場景在案例專案AsynchronousMessageTriggerAndRollbackTests單元測試得到測試。

simulateMessageSystemFailure() 方法通過破壞底層的JMS Session來模擬訊息系統失敗。預期的結果是事務部分提交:資料庫提交了,但訊息回滾。這個在 synchronousMessageTriggerAndPartialRollbackTests單元測試驗證過。

同樣,在AsynchronousMessageTriggerSunnyDayTests類中,包含一個所有事務成功提交的單元測試。

相 同的JMS配置,相同的業務邏輯同樣可以用在同步的環境中,訊息由儲存在業務邏輯中的阻塞請求所接收,而非監聽器容器。此方法在best-jms-db案 例專案中得到展示。 sunny-day case以及事務全部回滾分別在SynchronousMessageTriggerSunnyDayTests和 SynchronousMessageTriggerAndRollbackTests得到測試。

鏈式事務管理器

在其他的最大努力一階段提交模式案例中,一個粗糙的事務管理器實現僅僅是將一系列其他的事務管理器連結在一起,去實現事務同步。倘若業務處理成功,所有的事務將會提交, 否則它們都能回滾。

ChainedTransactionManager 接受一系列其他的事務管理器作為注入屬性,如清單8所示:

清單8、配置

<bean id="transactionManager">
       <property name="transactionManagers">
           <list>
               <bean>
                  <property name="dataSource" ref="dataSource" />
               </bean>
               <bean>
                   <property name="dataSource" ref="otherDataSource" />
               </bean>
           </list>
       </property>
</bean>

此配置簡單的測試,僅是同時插入資料到兩個資料庫,回滾,同時確保兩個執行回滾到最初狀態。此實現作為存在MulipleDataSourceTests中的一個單元測試,如同XA案例中的 atomikos-db專案。倘若回滾沒有同步,有事務提交了,那測試就算失敗。

記 住,資源順序很重要,它們是巢狀的,提交或者回滾以它們參與的相反順序進行。其中一個事務最為特別:如果存在問題,最重要的事務會回滾,即便是這問題是一個資源失敗。 同樣,testInsertWithCheckForDuplicates()測試方法展示了幕等式業務處理如何從部分失敗中保護系統,此實現作為一個裡層資源(otherDataSource)中業務運算防禦檢測。

int count = otherJdbcTemplate.update("UPDATE T_AUDITS ... WHERE id=, ...?");
if (count == 0) { count = otherJdbcTemplate.update("INSERT into T_AUDITS ...", ...); }

update首先嚐試和一個where子句執行,不出意外,update中的資料會插入資料庫中。本例中的幕等式處理一個額外的花銷是sunny-day case中額外的查詢,這個額外的花銷在複雜的每個事務執行多個查詢的業務處理中微乎其微。

其他選擇

案例中的ChainedTransactionManager擁有簡潔優勢,而且擴充套件優化已做的很好。另一個方式是, 當第二個資源加入時,利用Spring的TransactionSychronization API給當前事務 註冊一個回撥函式,此方式在best-jms-db案例中,最大的特點是TransactionAwareConnectionFactory和一個 DataSourceTransactionManager的結合。利用TransactionSynchronizationManager,這個特殊的案例可以擴充套件並且泛化到包含non-JMS的資源中。這樣理論上有個優勢,就是隻有加入事務的資源得到支援,而非鏈上的 所有資源。然而配置依舊需要監聽某個潛在的事務與之對應的資源。

同樣,Spring工程師團隊考慮將最大努力一階段提交事務管理器特性作為Spring核心。你可以在JJRA issue中投票,如果你喜歡這種模式,希望Spring中顯示以及更加透明地支援此種模式。

非事務訪問模式

非事務訪問模式需要一個特殊的業務處理,這樣才有意義。理想的狀態是有時,其中一些你需要訪問的資源邊緣化,一點都不需要事務。比如,或許你需要將一行資料插入一個 稽核表中,此操作是獨立的,和業務事務是否成功無關。僅僅記錄試圖做了某事。更普遍的場景,人們高估了他們需要對其中一個資源做讀寫的頻次,事實上,只有 訪問就很好了。 否則,寫操作需要得到很好地控制,因此如果發生任何錯誤,寫操作可以被記錄下來或者忽略。

在以上的案例中資源恰當地原理全域性事務,但仍然有其自己的本地事務,本地事務無需與其他發生的事情保持同步。如果你使用的是Spring,主要的事務由一個PlatformTransactionManager驅動, 邊緣資源或許是一個資料庫Connection,它來自一個不受事務管理器控制的DataSource。每一次訪問邊緣資源需要將預設環境設為 autoCommit=true。updates對讀操作不可見,前者可以與其他非提交事務併發進行,但寫操作帶來的影響通常對其他操作來說是立即可見的。

這個模式需要更多精細地分析,以及更多自信去涉及業務處理,但它同最大努力一階段提交沒 什麼區別。當任何事情出錯,一個通用的補償事務服務對多數專案來說太過龐大。不過簡單的用例所涉及的服務,它是幕等式的僅僅執行一個寫的操作,這種現象再 普通不過了。這些是非事務策略的理想場景。

Wing-and-a-Prayer:一種反模式

最後一種模式是一種反模式,它出現這樣一個場景中,開發者不理解或者沒有意識到他們已經存在一個分散式事務時。無需顯示的呼叫底層的資源事務API,你不確 定所有的資源是否在一個事務中。倘若用的是一個Spring事務管理器而非JtaTransactionManager,此管理器會將一個事務資源加入其中。這個事務管理器將會攔截Spring宣告事務管理特性的執行方法,比如@Transactional;其他的資源不會註冊到相同的事務中。通常的結局 是任何事情都運轉正常,不過很快使用者會發現存在一個異常,其中一個資源沒有回滾。一個典型的錯誤導致的問題是利用一個 DataSourceTransactionManager以及一個利用Hibernate實現的倉庫。

該用哪個模式呢?

我會通過分析已介紹過的模式其利弊,幫助大家做出取捨。第一步是分析你的系統是否需要分散式事務。一個必須但不充分條件是,存在不止一個事務資源的單一處理。充分條件是這些資源都在一個單獨的用例中,通常由系統架構的service層呼叫來驅動。

如果你不認為這是分散式事務,那最好採用Wing-and-a-Prayer模式,接著你會看見資料應該回滾但沒有。或許你會看到這種影響從失敗發生直至其下游一直存在,而且很難追溯回去。Wing-and-a-Prayer的 使用也可能會開發人員所疏忽,他們認為受到了XA保障,其實並沒與配置底層資源加入到事務中。我曾經做過一個這樣的專案,資料庫是其他人員搭建的,他們在 安裝資料庫的過程中關閉了XA支援。執行了個把月沒有任何問題,接著各種奇怪的失敗開始侵入業務處理中,需要花很長的時間去找出問題。

如果是一個包含異質資源的簡單用例,你可以分析甚至做一些重構,那麼非事務訪問模式或 許是個不錯的選擇,特別是其中一個幾乎是只讀資源,雙檢測確保寫操作。即便是失敗了,非事務資源中的資料在業務術語中必須有意義。稽核、版本控制、甚至日 志資訊能很好的切入到此目錄中,失敗變得相對很平常—-任何時間真實事務中的任何事情都可回滾,但你需確信這樣做不存在負面影響就好。

對系統而言,最大努力一階段提交需要通用的失敗保護機制,但有不存在2PC那麼大的開銷,而且效能得到極大的提升。相對非事務資源,它的建立需要更多的技巧,但無需太多的分析, 通常應用於更加通用的資料型別中。完成資料一致性的某些特性,需要保障業務處理對外層資源(”outer” resources:第一個提交的資源)而言是幕等式。訊息驅動的資料庫更新就是一個完美的例子,並且在Spring中得到很好的支援。不常見的場景需要一些額外的框架程式碼,這些程式碼終究會成為Spring的一部分。

共享資源模式是一種特定的例子,通常涉及一個特定的型別和平臺兩個資源,比如,ActiveMQ和任何一個RDBMS或者OracleAQ與一個Oracle資料庫共存。這樣做最大的收益是相當的靈活以及出色的效能。

Full XA with 2PC是一種通用模式,在應對多個異質資源事務 失敗時提供很好的無憂保證。不利的是它的開銷很大,需要遵循特定的I/O協議和特定的平臺。有開源的JTA實現,提供了一種擺脫應用伺服器的方式,但多數 開發人員依舊認為它們並非最好。可以確信的是,人們花更多的時間去思考系統的事務界限,會更傾向於使用他們並不那麼需要的JTA和XA。至少使用 Spring的開發人員,他們的業務邏輯無需知道事務如何被處理的,暫時無需考慮平臺選擇的問題。

相關文章