[譯] Spring 的分佈式事務實現 — 使用和不使用 XA — 第一部分

Spring 的 7 種事務處理模式html

雖然在 Spring 中分佈式事務一般使用 Java Transaction API 和 XA 協議實現,但也有其餘的實現方式。最好的實現方式取決於應用程序所使用資源的類型,以及你是否願意在性能、安全性、可靠性和數據完整性之間作出權衡。針對這個 Java 中的典型問題,Spring 的開發者 David Syer 將會介紹 7 種 Spring 分佈式應用的實現方式,其中 3 種實現使用了 XA 協議,另外 4 種使用了其餘的實現方式。(中級知識點)前端

Spring 框架對 Java Transaction API (JTA) 的支持使應用程序可以無需在 Java EE 容器中便可使用分佈式事務和 XA 協議。然而,即便有了這種支持,XA 的性能開銷仍然很大,並且可能不可靠而且難於管理。不過使人驚喜的是,某種特定類型的應用程序能夠徹底避免使用 XA 來實現分佈式事務。java

爲了讓你對分佈式事務的各類實現方式有充分的理解和思考,我將詳細分析這 7 種事務處理模式,並提供代碼示例幫助你理解得更具體。我將根據安全性和可靠性來依次介紹這些模式,從一般來講數據完整性和原子性程度最高的模式開始。當你按順序瀏覽時,你會看到愈來愈多的警示說明和限制條件。這些模式的性能開銷也大體相反(從開銷最大的模式開始)。與編寫業務代碼徹底不一樣的是,這些模式都是從架構複雜度和技術難度考慮的,因此我不會關心業務用例,只關心使每種模式正常工做的最小代碼量。android

注意,只有前三種模式涉及 XA。而從性能的角度考慮,這些模式可能沒法使用或性能差到不可接受。我不會像介紹其餘模式那樣對 XA 模式有詳細的討論,由於 XA 在其餘地方已經有不少介紹了,不過我提供了第一個模式(基於 XA)的簡單示例。經過閱讀本文,你將瞭解使用分佈式事務能夠作什麼、不能作什麼,什麼時候使用 XA、什麼時候不使用 XA,以及如何避免使用 XA。ios

分佈式事務及其原子性

一個分佈式事務一般包含多個事務資源。事務資源是指關係型數據庫和消息中間件的鏈接。一個典型的事務資源都會有像 begin()rollback()commit() 這樣的 API。在 Java 中,一個事務資源一般表現爲底層鏈接工廠提供的實例:對於數據庫來講,就是 Connection 對象(由 DataSource 提供)或是 Java Persistence API(JPA)的 EntityManager 對象;對於 Java Message Service(JMS)來講,則是 Session 對象。git

在一個典型的例子中,一個 JMS 消息觸發了數據庫的更新。根據時間前後順序,一次成功的交互過程以下:github

  1. 啓動消息事務
  2. 接收消息
  3. 啓動數據庫事務
  4. 更新數據庫
  5. 提交數據庫事務
  6. 提交消息事務

若是數據庫在更新數據時報錯(如約束衝突),理想的交互順序以下:spring

  1. 啓動消息事務
  2. 接收消息
  3. 啓動數據庫事務
  4. 更新數據庫失敗!
  5. 回滾數據庫事務
  6. 回滾消息事務

在這個例子中,消息在最後回滾完成以後回到了中間件,在某個時刻將再次提交到另外一個事務中。這一般是一件好事,由於若是這樣作的話更新數據時發生的錯誤將會被記錄下來。(自動重試和異常處理的機制超出了本文的討論範圍。)數據庫

上述兩個例子中最重要的特色就是原子性,邏輯上來講,一個事務要麼徹底成功,要麼徹底失敗。apache

那麼是什麼保證了上面兩個例子在流程上的一致性呢?咱們必須在事務資源之間進行一些同步,以便在一個事務提交以後,另外一個事務才能提交。不然,整個事務就不是原子性的。由於涉及多個資源,因此事務是分佈式的。若是不進行同步,事務就不會是原子性的。分佈式事務的理論和實現上的困難都與資源的同步(或缺乏資源)有關。

下面討論的前三個模式都是基於 XA 協議的。因爲這些模式已經被普及,因此在這裏我不會介紹得很詳細。若是你對 XA 的模式很是熟悉,你能夠直接跳到共享事務資源模式

完整的 XA 協議與兩階段提交(2PC)

若是你須要確保應用程序的事務在服務器宕機(服務器崩潰或斷電)以後仍可以恢復,那麼完整的 XA 協議是你惟一的選擇。在下面的例子中,用於同步事務的共享資源是一個特殊的事務管理器,它使用 XA 協議協調了進程的信息。在 Java 中,從開發者的角度來看,該協議是經過 JTA 的 UserTransaction 對象暴露出來的。

做爲一個系統接口,XA 是大多數開發者從未見過的一種底層技術。開發者須要知道 XA 協議的存在、它能作什麼、性能消耗如何,以及它是如何操做事務資源的。性能消耗來自於兩階段提交(2PC)協議,事務管理器使用該協議來確保全部資源能在事務結束前就事務的結果達成一致。

若是應用程序是基於 Spring 構建的,它將使用 Spring 中的 JtaTransactionManager 和 Spring 聲明性事務管理來隱藏底層同步的細節。對於開發者來講,使用 XA 與否取決於工廠資源的配置方式:在應用程序中如何配置 DataSource 實例和事務管理器。本文包含了一個示例應用程序(atomikos-db 項目),它演示了這種配置方式。該應用程序中只有 DataSource 實例和事務管理器是基於 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);

    // 數據的變動將在此方法退出後回滾

  }
複製代碼

而後 MulipleDataSourceTests 將會驗證這兩個操做都回滾完成,如清單 2 所示:

清單 2. 驗證回滾

@AfterTransaction
  public void checkPostConditions() {

    int count = getJdbcTemplate().queryForInt("select count(*) from T_FOOS");
    // 該數據變動已被測試框架回滾
    assertEquals(0, count);

    count = getOtherJdbcTemplate().queryForInt("select count(*) from T_AUDITS");
    // 因爲 XA 的存在,該數據變動也被回滾了
    assertEquals(0, count);

  }
複製代碼

爲了更好地理解 Spring 事務管理的工做原理以及配置的方式,請參閱 Spring 參考文檔

XA 與 1PC 優化

這種模式經過避免 2PC 的性能開銷對許多隻包含單資源事務的事務管理器進行了優化。你將會但願你的應用程序服務可以藉此解決這個問題。

XA 與最終資源策略

XA 事務管理器的另外一個特性是,當除某一個資源外的全部資源都支持 XA 時,它仍然能夠提供與全部資源都支持 XA 時相同的數據恢復保證。經過對資源進行排序,並使非 XA 資源參與決策來實現該特性。若是提交失敗,則回滾全部其餘資源。這幾乎是 100% 的徹底性保證,但還不夠完美。當提交失敗時,除非採起額外的措施(在一些高端實現中有這樣的實現),不然報錯的跟蹤信息會不多。

共享事務資源模式

在某些系統中,爲了下降複雜性和增長吞吐量,一種較好的模式是經過確保系統中的全部事務資源實際上都是同一個資源的不一樣形式,從而徹底消除對 XA 的依賴。顯然,這在全部的用例中都是不可能的,但這種模式與 XA 同樣可靠,並且一般要快得多。這樣的共享事務資源模式是足夠可靠的,但只限於某些特定的平臺和處理場景。

有一個這種模式的簡單例子對不少人來講都很熟悉,即在對象關係映射(ORM)組件和 JDBC 組件之間共享數據庫的 Connection。這就是你使用支持 ORM 工具的 Spring 事務管理器時所發生的事情,如 HibernateEclipseLinkJava Persistence API(JPA)。同一個事務能夠安全地跨 ORM 和 JDBC 組件使用,該執行過程一般由控制事務的服務級方法來實現。

該模式的另外一個有效用法是單個數據庫的消息驅動更新(如本文中介紹的簡單例子所示)。消息中間件系統須要將數據存儲在某個地方,一般是關係數據庫中。要實現此模式,只需指定消息傳遞系統的目標數據庫爲同一個業務數據庫便可。此模式須要消息中間件的供應商公開其存儲策略的詳細信息,以即可以將其配置指向相同的數據庫並掛接到相同的事務中。

並非全部的供應商都能作到這一點。另外一種適用於幾乎全部數據庫的方式,是使用 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 的持久化策略,它將業務數據源的消息系統鏈接到同一個 DataSource,用於接收消息的 Spring JmsTemplate 上的標誌位也一樣重要。配置 ActiveMQ 持久化策略的方式如清單 4 所示:

清單 4. ActiveMQ 的持久化配置

<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory" depends-on="brokerService">
  <property vm://localhost?async=false" />
</bean>

<bean id="brokerService" class="org.apache.activemq.broker.BrokerService" init-method="start" destroy-method="stop">
    ...
  <property >
    <bean class="org.apache.activemq.store.jdbc.JDBCPersistenceAdapter">
      <property >
        <bean class="com.springsource.open.jms.JmsTransactionAwareDataSourceProxy">
          <property />
          <property />
        </bean>
      </property>
      <property />
    </bean>
  </property>
</bean>

複製代碼

用於接收消息的 Spring JmsTemplate 上的標誌位配置如清單 5 所示:

清單 5. 爲事務配置 JmsTemplate

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  ...
  <!-- 這很重要... -->
  <property />
</bean>
複製代碼

若是沒有設置 sessionTransacted=true,就永遠不會執行 JMS 會話事務的 API 調用,而且消息的接收將沒法回滾。這裏重要的一點是嵌入式消息代理服務器中的特殊參數 async=false 和對 DataSource 的包裝,他們共同確保了 ActiveMQ 和 Spring 共同使用了同一個 JDBC 事務的 Connection

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索