分散式事務淺析

語言: CN / TW / HK

前言

分散式事務拆開來其實就是分散式、事務兩個概念,分散式簡單講就是不同程序間的系統進行通訊;事務狹義上我們經常把它看作是資料庫的事務,事務具有ACID特性即:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、永續性(Durability),通俗來說就是同一個事務中對於資料庫的更新操作來說要麼都成功,要麼都失敗;廣義上來說同一個事務中的一批操作(非侷限於資料庫操作)要麼都成功,要麼都失敗;綜合來說就是事務的參與者分別位於不同的程序節點中。

分散式理論

既然和分散式有關,那我們很有必要了解一下分散式系統的幾個理論CAP和Base理論;

CAP理論

一個分散式系統不可能同時滿足一致性(Consistency)、可用性(Availability)和分割槽容錯性(Partition tolerance)這三個基本需求,最多隻能同時滿足其中的兩項;

一致性:所有節點在同一時間具有一致的資料,以下單為例:使用者訂單生成,庫存減少,使用者付錢等操作要麼一起成功要麼一起失敗; 可用性:服務一直可用,而且是正常響應時間; 分割槽容錯性:分散式系統在遇到某節點或網路分割槽故障的時候,仍然能夠對外提供滿足一致性和可用性的服務。

對於一個分散式系統而言,分割槽容錯性可以說是一個最基本的要求;所以大部分情況其實都是在CA兩者之間進行權衡;分散式事務同樣存在這樣的抉擇,比如下面要介紹的X/Open XA協議,更加傾向於一致性;但是面對網際網路的高併發,這種強一致性引發了很大的效能問題,而基於BASE理論的柔性事務可能是更好的一個選擇;

BASE理論

BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的簡寫。很明顯BASE理論更加傾向滿足CAP理論中的AP,既滿足可用性,分割槽容忍性的系統,通常可能對一致性要求低一些;

BASE理論和ACID可以說是完全相反的,ACID保證的是強一致性犧牲可用性,BASE理論是用最終一致性代替強一致性保證可用性;在實際的場景中,不同的業務對資料的要求是不一樣的,所以大部分情況下BASE理論和ACID是結合起來使用的;

基於BASE理論的柔性事務典型方案:最大努力通知型,TCC,非同步確保型等;

關於CAP,ACID,BASE理論可以通過如下圖做一個直觀的瞭解:

165514_z2BO_159239.jpg

分散式事務場景

在介紹分散式事務場景之前,我們首先對本地事務做一個簡單的瞭解,也是我們平時最常用的事務,基本上主流的資料庫都支援本地事務,也基本上都滿足ACID這幾個特性;

本地事務

一個程序對應一個數據庫,事務的所有參與者都在一個程序內,並且對同一臺數據庫進行操作;

image-20210312151622159.png

java.sql.Connection提供了對事務的提交、回滾等操作,大致的程式碼如下所示:

Connection conn = DriverManager.getConnection(...) //獲取資料庫連線
// JDBC中預設是true,自動提交事務,false關閉自動提交
conn.setAutoCommit(false);
try {
	// 資料庫操作1
	// 資料庫操作2
	conn.commit(); // 提交事務
} catch (Exception e) {
	conn.rollback();// 事務回滾
} finally {
	conn.close();// 關閉連結
}

當然java本身沒有提供對事務的支援,所有支援都是資料庫提供的,比如mysql相關事務操作:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t_order0 (user_id,order_id) values ('110','1212');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t_order0 (user_id,order_id) values ('111','1213');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.03 sec)

當然如果每塊業務處理事務都要這麼寫一遍實在是麻煩,完全可以通過AOP做切面處理,Spring就可以很好的幫我們完成事務的處理,提供了統一的事務管理介面PlatformTransactionManager,常見的實現類:

DataSourceTransactionManager:用於管理本地事務;

JtaTransactionManager:支援分散式事務實現了JTA規範,使用XA協議進行兩階段提交;

<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
</bean>

<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
	<tx:attributes>
		......
	</tx:attributes>
</tx:advice>

<aop:config>
	......
</aop:config>

既可以通過切面配置哪些類哪些方法需要做事務處理,也可以通過@Transitional註解來配置;

分散式事務

隨著SOA架構、微服務的流行,分散式事務也出現在很多場景中,常見的包括:單應用多資料來源、多應用單資料來源、多應用多資料來源;

單應用多資料來源

這種情況可能是對不同的業務對資料庫做了拆分,但是應用還沒做拆分,或者說資料量比較大對資料庫做了分庫分表操作;

image-20210312151706870.png

注:這裡的資料來源不一定就是資料庫,也可能是MQ之類的資料來源;

多應用單資料來源

這種情況很多出現在使用Oracle資料庫的系統中,資料庫功能強大,多個應用共用一個數據庫,常見於很多金融系統;

image-20210312153435867.png

多應用多資料來源

這種情況常見於各種SOA架構、微服務系統中,應該說是目前最常見的場景,各個程序間通過RPC呼叫;

image-20210312153033936.png

以上幾種場景都會用到分散式事務,當然上面也提到CAP理論,對於分散式事務的處理方式也是會根據你的業務邏輯做相應的權衡;但是不得不提DTP模型,可以說是分散式事務處理的標準;

DTP模型與XA規範

DTP模型以及XA規範是一家叫X/Open的組織定義的行業標準;

X/Open國際聯盟有限公司是一個歐洲基金會,它的建立是為了向UNIX環境提供標準。它主要的目標是促進對UNIX語言、介面、網路和應用的開放式系統協議的制定。它還促進在不同的UNIX環境之間的應用程式的互操作性,以及支援對電氣電子工程師協會(IEEE)對UNIX的可移植作業系統介面(POSIX)規範。

相關規範可以參考如下文件:

DTP(Distributed Transaction Processing)模型:Distributed Transaction Processing: Reference Model

DTP(Distributed Transaction Processing)XA規範:Distributed Transaction Processing: The XA Specification

DTP模型

X/Open DTP模型包括五個基本功能元件:

  • 應用程式(AP):全稱Application Program,定義事務邊界並指定構成事務的操作;
  • 資源管理器(RMs):全稱Resource Managers ,如資料庫或檔案訪問系統,提供對資源的訪問;
  • 事務管理器(TM):全稱Transaction Manager,為事務分配識別符號,監控其進度,並負責事務完成和協調故障恢復;
  • 通訊資源管理器(CRM):全稱Communication Resource Managers,用於控制TM域內或跨TM域的分散式應用程式之間的通訊;
  • 通訊協議(CP):全稱Communication protocol,提供分散式應用程式使用並由CRM支援的底層通訊服務。

模型例項

每個例項可以支援一個AP、一個TM和多個RMs。分散式應用程式由兩個或多個例項表示,每個例項中都包含一個CRM;

image-20210312170900096.png

這種模型可以說是最簡單的模型了,對應的其實就是上面的單應用多資料來源的情況;而對於分散式應用程式需要多個例項,模型如下:

image-20210312171554738.png

可以發現每個模型例項多了一個CRM模組,主要用於分散式應用程式間的通訊;可以用來解決多應用的情況;

事務管理器作用域

TM域由一個或多個使用同一TM的例項組成,此TM對於在該TM域中執行的所有應用程式都是通用的。公共TM使用邏輯共享的資料結構和日誌進行全域性事務管理;

image-20210312174450915.png

當兩個例項之間發生分散式通訊時,它們具有上下級關係,請求另一個例項參與全域性事務的例項稱為上級,請求的例項稱為從屬例項,特別是沒有上級的例項稱為根;在X/opendtp模型中,通過使用樹結構來管理跨分散式ap操作的全域性事務;

image-20210312174918295.png

XA規範

下圖顯示了DTP系統的本地例項,其中AP呼叫TM來構造事務。這些框表示X/Open DTP模型中的軟體元件;箭頭指示控制流的方向;

image-20210315160523423.png

X/Open規範的主題是上圖中的介面(3),即TMs和RMs互動的XA介面;

image-20210315161055116.png

XA介面

RM和TM之間的介面定義如下所示:

image-20210315161713630.png

  1. ax_reg:向TM註冊RM;
  2. ax_unreg:用TM登出RM;
  3. xa_close:終止AP對RM的使用;
  4. xa_commit:告訴RM提交事務分支;
  5. xa_complete:測試非同步xa_ 操作是否完成;
  6. xa_end:取消執行緒與事務分支的關聯;
  7. xa_forget:允許RM放棄其對啟發式完成事務分支的瞭解;
  8. xa_open:初始化RM以供AP使用;
  9. xa_prepare:要求RM準備提交事務分支;
  10. xa_recover:獲取RM已準備或啟發式完成的XID列表;
  11. xa_rollback:告訴RM回滾事務分支;
  12. xa_start:啟動或恢復事務分支-將XID與RM的執行緒請求的未來工作相關聯

二階段提交

XA規範的基礎是二階段提交協議,XA規範對二階段提交做了優化;二階段提交其實就是將提交分成兩個階段,下面大致看一下二階段提交的流程:

image-20210315193542695.png

image-20210315193749914.png

第一階段:預提交階段 1.事務詢問:協調者會問所有的參與者節點,是否可以執行提交操作 2.執行事務:各個參與者執行事務操作 如:資源上鎖,將Undo和Redo資訊記入事務日誌中 3.參與者向協調者反饋事務詢問的響應:如果參與者成功執行了事務操作,反饋給協調者Yes響應,否則No響應

第二階段:執行事務提交 假如協調者從所有的參與者獲得的反饋都是Yes響應,那麼就會執行事務提交 1.傳送提交請求:協調者向參與者傳送Commit請求 2.事務提交:參與者接受到Commit請求後,會正式執行事務提交操作,並在完成提交之後釋放事務資源 3.反饋事務提交結果:參與者在完成事務提交之後,向協調者傳送Ack訊息 4.完成事務:協調者接受到所有參與者反饋的Ack訊息後,完成事務

假如任何一個參與者向協調者反饋了No響應,或者在等待超時之後,協調者尚無接收到所有參與者的反饋資訊,那麼就會中斷事務 1.傳送回滾請求:協調者向參與者傳送Rollback請求 2.事務回滾:參與者利用Undo資訊來執行事務回滾,並釋放事務資源 3.反饋事務回滾結果:參與者在完成事務回滾之後,向協調者傳送Ack訊息 4.中斷事務:協調者接收到所有參與者反饋的Ack訊息之後,中斷事務

因為二階段提交本身存在著阻塞、單點等問題,後續出現了改進版本三階段提交,將第一階段一分為二,此處不在詳細介紹;

標準定義好之後,各種RM產品就要去實現這些介面,這樣就可以支援分散式事務,最典型的RM包括資料庫,MQ等;在一個分散式事務中RM往往是有多個的,每一個RM提供的XA支援,可以理解為一個事務分支,統一交給TM來管理;

Mysql XA支援

要想檢視當前Mysql是否提供了XA支援,可以直接使用如下命令:

image-20210315165539430.png

其中的XA用來表示是否支援,InnoDB引擎是支援的,其他不支援;

XA事務SQL語句

XA {START|BEGIN} xid [JOIN|RESUME]  //開啟XA事務

XA END xid [SUSPEND [FOR MIGRATE]]  //結束XA事務

XA PREPARE xid  //準備提交

XA COMMIT xid [ONE PHASE]  //提交事務

XA ROLLBACK xid  //回滾事務

XA RECOVER  //列出所有處於PREPARE階段的XA事務

更多可以參考:Mysql XA文件

下面是一個簡單的XA事務,它將行作為全域性事務的一部分插入表中:

mysql> XA START 'xatest';
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO mytable (i) VALUES(10);
Query OK, 1 row affected (0.04 sec)

mysql> XA END 'xatest';
Query OK, 0 rows affected (0.00 sec)

mysql> XA PREPARE 'xatest';
Query OK, 0 rows affected (0.00 sec)

mysql> XA COMMIT 'xatest';
Query OK, 0 rows affected (0.00 sec)

ActiveMQ XA支援

ActiveMQ同樣提供了XA相關命令支援,如下所示:

    public static final byte BEGIN = 0;  //開啟事務
    public static final byte PREPARE = 1;  //準備提交
    public static final byte COMMIT_ONE_PHASE = 2;  //一階段提交
    public static final byte COMMIT_TWO_PHASE = 3;  //二階段提交
    public static final byte ROLLBACK = 4;  //回滾事務
    public static final byte RECOVER = 5;  //列出所有處於PREPARE階段的XA事務
    public static final byte FORGET = 6;  //允許RM放棄其對啟發式完成事務分支的瞭解
    public static final byte END = 7;  //結束XA事務

ActiveMQ通過以上位元組標識來表達不同的XA介面型別;

各類RM已經提供了對XA協議的支援,為了讓開發人員更好的使用,以Java為例,Java提供了JTA規範,各類RM同樣需要去實現JTA的相關規範介面,下面重點看看JTA規範;

JTA規範

JTA全稱:Java Transaction API,Java事務API可以認為是XA規範的Java版本,為JEE平臺提供了分散式事務功能,模型圖如下所示:

image-20210315201027505.png

可以發現和XA規範比較多了一個Application Server,其實就是的web容器,常見的比如:tomcat,weblogic,jboss,websphere等;除了tomcat其他幾個容器其實都實現了JTA規範,可以提供事務管理器的功能;像tomcat這種沒有提供事務管理的容器可以藉助第三方分散式事務管理器比如:Atomikos等;

注:JTA規範並沒有指定與通訊相關的介面,有關TM之間互操作性的更多細節,請參閱JTS規範;

更多:JTA文件

介面定義

Java將所有事務的規範都定義在了JTA包中,裡面只有介面沒有實現,可以發現此jar包最新版為1.1,2008年之後就沒有更新過,想要看詳細的原始碼直接引入即可:

<dependency>
	<groupId>javax.transaction</groupId>
	<artifactId>jta</artifactId>
	<version>1.1</version>
</dependency>

具體原始碼如下所示,幾個核心介面類用紅框標出:

image-20210316091208630.png

一共8個介面分別如下:

  1. XAResource:RM需要實現的介面,定義RM提供給TM操作的介面;
  2. Xid:事務ID;
  3. Status:事務狀態,一共10個狀態;
  4. Sychronization:同步介面;
  5. Transaction:事務介面;
  6. TransactionManager:事務管理器,管理事務的全生命週期
  7. TransactionSynchronizationRegistry:事務同步註冊;
  8. UserTransaction:給使用者使用的事務客戶端,裡面封裝了使用者可以使用事務的介面;

以上這些介面其實都不用開發者去實現,一般由RM和TM的廠商去實現:

XAResource,Xid介面:由RM廠商實現,常見的比如資料庫廠商,MQ廠商等;

TransactionManager,UserTransaction介面:可以由web容器去實現如jboss,weblogic等,也可以由第三方去實現如:Atomikos等;

RM實現

常見的RM包括資料庫、MQ;下面以Mysql和AcitveMQ為例子來看一下是如何使用的;

資料庫

在上面的章節中我們已經介紹了Mysql 對XA的支援,也就是Mysq廠商已經提供了對相關功能的支援,其實下面要做的就是驅動程式提供對Mysql XA功能的保證,同時需要實現JTA中XAResource,Xid相關介面;

com.mysql.jdbc.jdbc2.optional.MysqlXAConnection   --> XAResource
com.mysql.jdbc.jdbc2.optional.MysqlXid            --> Xid

以上是mysql驅動程式中對JTA支援的兩個核心類,具體使用也比較簡單:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.XAConnection;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import com.mysql.jdbc.jdbc2.optional.MysqlXAConnection;
import com.mysql.jdbc.jdbc2.optional.MysqlXid;

public class XAMysql {

	public static void main(String[] args) throws SQLException {
		// 列印XA語句用於除錯
		boolean logXaCommands = true;
		Connection conn1 = DriverManager.getConnection("jdbc:mysql://localhost:3306/ds0", "root", "root");
		XAConnection xaConn1 = new MysqlXAConnection((com.mysql.jdbc.ConnectionImpl) conn1, logXaCommands);
		XAResource rm1 = xaConn1.getXAResource();

		Connection conn2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/ds1", "root", "root");
		XAConnection xaConn2 = new MysqlXAConnection((com.mysql.jdbc.ConnectionImpl) conn2, logXaCommands);
		XAResource rm2 = xaConn2.getXAResource();

		// 全域性事務id
		byte[] gid = "global".getBytes();
		int formatId = 1;
		try {
			// 事務分支1
			byte[] bqual1 = "b1".getBytes();
			Xid xid1 = new MysqlXid(gid, bqual1, formatId);
			rm1.start(xid1, XAResource.TMNOFLAGS);
			PreparedStatement ps1 = conn1
					.prepareStatement("insert into t_order0 (user_id,order_id) values ('110','1212')");
			ps1.execute();
			rm1.end(xid1, XAResource.TMSUCCESS);

			// 事務分支2
			byte[] bqual2 = "b2".getBytes();
			Xid xid2 = new MysqlXid(gid, bqual2, formatId);
			rm2.start(xid2, XAResource.TMNOFLAGS);
			PreparedStatement ps2 = conn2
					.prepareStatement("insert into t_order0 (user_id,order_id) values ('111','1213')");
			ps2.execute();
			rm2.end(xid2, XAResource.TMSUCCESS);

			// 兩階段提交
			int rm1_prepare = rm1.prepare(xid1);
			int rm2_prepare = rm2.prepare(xid2);
			if (rm1_prepare == XAResource.XA_OK && rm2_prepare == XAResource.XA_OK) {
				rm1.commit(xid1, false);
				rm2.commit(xid2, false);
			} else {
				rm1.rollback(xid1);
				rm2.rollback(xid2);
			}
		} catch (XAException e) {
			e.printStackTrace();
		}
	}
}

以上不僅介紹了XAResource是如何去使用的,同時也簡單模擬了分散式事務管理的功能,只有在多個數據源都準備好的情況下才能提交事務,否則回滾事務,這部分其實應該交給更加專業的元件去實現;

MQ

MQ廠商很多,不一定每個MQ都實現了JTA的相關介面,下面要介紹的AcitveMQ實現了JTA的RM介面:

org.apache.activemq.TransactionContext      --> XAResource
org.apache.activemq.command.XATransactionId --> Xid

下面看一個簡單的使用例項:

import javax.jms.JMSException;
import javax.jms.XAQueueConnection;
import javax.jms.XAQueueConnectionFactory;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.apache.activemq.ActiveMQXASession;
import org.apache.activemq.TransactionContext;
import org.apache.activemq.command.XATransactionId;

public class MQXATest {

	public static void main(String[] args) throws XAException, JMSException {
		XAQueueConnectionFactory factory = new ActiveMQXAConnectionFactory("tcp://localhost:61616");
		XAQueueConnection qConnection = factory.createXAQueueConnection();
		qConnection.start();
		ActiveMQXASession session = (ActiveMQXASession) qConnection.createXAQueueSession();
		// TransactionContext實現XAResource
		TransactionContext tc = session.getTransactionContext();

		// XATransactionId實現Xid
		Xid xid = new XATransactionId();
		tc.start(xid, XAResource.TMSUCCESS);
		tc.end(xid, XAResource.TMSUCCESS);
		int prepare = tc.prepare(xid);
		if (prepare == XAResource.XA_OK) {
			tc.commit(xid, false);
		} else {
			tc.rollback(xid);
		}
	}
}

以上大致介紹了一下常見的RM廠商對JTA介面的支援,總體上分為兩步:第一步廠商提供對XA介面的支援,第二步方便Java使用者使用提供實現JTA介面的客戶端程式(比如驅動程式,客戶端jar包等);

TM實現

對於事務管理器的實現上面也說到主要包括:web容器實現、第三方實現;

web容器

這裡以JBoss為例做一個簡單介紹,JBoss事務相關主要在jboss-transactionjboss-transaction-spi中,核心類主要包括:

org.jboss.tm.TxManager                                    --> TransactionManager
org.jboss.tm.usertx.client.ServerVMClientUserTransaction  --> UserTransaction

相關配置這裡就不做過多介紹,下面重點看一下第三方實現方式,以Atomikos為例;

Atomikos

Atomikos是一家公司的名字,提供了基於JTA規範的XA分散式事務TM的實現,包含兩個產品:

  1. TransactionEssentials:開源的免費產品;
  2. ExtremeTransactions:商業版收費產品;

兩個產品的關係如下圖所示:

d4dffdf08cdeaae2760319a79098fd9b.png

商業版本提供了更多額外的功能:

  • 支援TCC:柔性事務的支援,Try-Confirm-Cancel;
  • 支援通過RMI、IIOP、SOAP這些遠端過程呼叫技術,進行事務傳播;這其實就可以用在微服務常見的場景中:多應用多資料來源的情況;

Atomikos同樣提供了對UserTransaction、TransactionManager介面的實現:

com.atomikos.icatch.jta.UserTransactionImp
com.atomikos.icatch.jta.TransactionManagerImp

下面看一個簡單的分散式管理器的使用:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Properties;

import javax.transaction.UserTransaction;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.jdbc.AtomikosDataSourceBean;

public class AtomikosTest {

	public static void main(String[] args) {
		AtomikosDataSourceBean ds1 = createAtomikosDataSourceBean("t_order0");
		AtomikosDataSourceBean ds2 = createAtomikosDataSourceBean("t_order1");

		Connection conn1 = null;
		Connection conn2 = null;
		PreparedStatement ps1 = null;
		PreparedStatement ps2 = null;

		UserTransaction userTransaction = new UserTransactionImp();
		try {
			// 開啟事務
			userTransaction.begin();

			// 執行分支1
			conn1 = ds1.getConnection();
			ps1 = conn1.prepareStatement("insert into t_order0 (user_id,order_id) values ('110','1212')");
			ps1.execute();

			// 執行分支2
			conn2 = ds2.getConnection();
			ps2 = conn2.prepareStatement("insert into t_order1 (user_id,order_id) values ('111','1213')");
			ps2.execute();

			// 兩階段提交
			userTransaction.commit();
		} catch (Exception e) {
			// 異常回滾
			userTransaction.rollback();
		} finally {
			// 關閉連線
		}
	}

	private static AtomikosDataSourceBean createAtomikosDataSourceBean(String dbName) {
		Properties p = new Properties();
		p.setProperty("url", "jdbc:mysql://localhost:3306/" + dbName);
		p.setProperty("user", "root");
		p.setProperty("password", "root");

		AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
		ds.setUniqueResourceName(dbName);

		// MySQL驅動XAResource實現類
		ds.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
		ds.setXaProperties(p);
		return ds;
	}
}

這裡使用的RM是資料庫Mysql,可以發現UserTransaction對MysqlXADataSource做了管理,進行統一的事務提交,事務回滾;對比之前XAMysql例項中手動提交每個RM,可以說更加方便簡潔;

JTS規範

上文中也提到JTA規範並沒有指定與通訊相關的介面,有關TM之間互操作性的更多細節,需要使用JTS規範;看看官方的具體定義:

JTS規定了事務管理器的實現,事務管理器在高層支援JTA規範,在底層實現OMG物件事務服務(OTS)規範的Java對映。JTS使用CORBA OTS介面實現互操作性和可移植性(即,共傳輸性和可移植性)。這些介面為任何使用IIOP(internetinterorb協議)在JTS事務管理器之間生成和傳播事務上下文的實現定義了一種標準機制。注意,這也允許使用IIOP傳輸機制上的其他API;例如,允許IIOP上的RMI。

JTS個人感覺更像是事務管理器實現上的規範,其中重點提到了支援通過RMI、IIOP、SOAP這些遠端過程呼叫技術,進行事務傳播;

更多:JTS文件

其他實現

以上重點介紹了DTP模型、XA規範,Java根據此規範定義的JTA規範,方便Java開發者使用;以及圍繞這一套模型各種廠商或者一些第三方公司對其做的各種支援,實現了一種比較通用型的分散式事務處理方式;當然上面也提到這種方式其實更傾向於CAP中的CP,是一種強一致性的實現方式,所以在面對高併發的情況下,效能是其一大缺陷;越來越多的網際網路公司可能更加傾向於最終一致性實現的分散式事務方案,常見的方案有:TCC,事務訊息,本地事務表等等;

TCC

TCC全稱:Try,Confirm,Cancel分成三個操作:

Try:資源的預留;

Confirm:確認操作,真正的執行,類似提交;

Cancel:撤銷操作,類似回滾操作;

其實可以發現和二階段提交在流程上很像,都是先去試探,然後再提交或回滾;二者的區別:2PC更多的是面向資源(資料庫,MQ等),而TCC可以面向業務層,範圍更廣;另外2PC會鎖定資源,而TCC可以不需要,實現最終一致性;當然TCC實現起來也更加複雜,對業務的侵入也比較大;

事務訊息

一些MQ支援事務訊息如:RocketMQ,傳送的訊息和其他本地事件需要同時成功同時失敗,下面看一下它的流程:

  1. 生產者傳送"待確認"訊息;
  2. RocketMQ接收到訊息進行相關儲存操作,成功以後返回狀態給生產者;
  3. 生產者接收到的返回如果為SEND_OK狀態,將執行本地事務操作;
  4. 根據本地事務執行的結果,生產者執行commit還是rollback;
  5. 如果第四步生產者執行操作失敗,伺服器會在經過固定時間段對狀態為"待確認"的訊息發起回查請求;
  6. 生產者接收到回查請求後根據本地事務的結果返回commit還是rollback;
  7. 伺服器收到結果後執行相關操作。

可以發現RocketMQ實現的也是最終一致性;

本地事務表

本地事務表:利用各系統的本地事務來實現分散式事務;將大事務分成小事務來處理;

將本地的業務和訊息插入訊息表的操作放到同一個事務中,本地事務中肯定是可以保證一起成功一起失敗的;

接下來可以採用MQ由對方訂閱訊息並監聽,有訊息時自動觸發事件;或者定時輪詢掃描的方式,去檢查訊息表的資料;

消費端最好保證冪等性,防止重複通知;

一階段

一階段:簡單理解就是如果由多個數據源操作,那麼就一個一個提交,經常出現在單系統多資料來源的情況;這種模式在特殊情況下是可能出現事務不一致的情況;spring-data-common中的ChainedTransactionManager就提供了類似的功能:鏈式事務;這種模式如果在一些內部系統,網路與硬體環境一般比較穩定,硬體發生故障的概率較小,可以嘗試;

Seata

阿里提供的一款開源的分散式事務解決方案,致力於在微服務架構下提供高效能和簡單易用的分散式事務服務;

Seata 是一款開源的分散式事務解決方案,致力於提供高效能和簡單易用的分散式事務服務。Seata 將為使用者提供了 AT、TCC、SAGA 和 XA 事務模式,為使用者打造一站式的分散式解決方案。

這裡就不過多介紹了,官方文件介紹的非常全面;

更多:Seata

總結

本文大致對分散式事務所涉及的點都做了一個簡單的介紹,可以把它當作一個大綱性的東西去看,裡面很多點只有在真正使用的時候才能發現一些其中的問題;分散式事務可以說是所有技術中的一個難點,解決的方案也很多,這個需要根據業務的實際情況,做出最合適的選擇,這個過程其實也是挺難的,你需要了解每種方案它的優缺點,它適用的範圍,沒有一個萬能的方案。

感謝關注

可以關注微信公眾號「回滾吧程式碼」,第一時間閱讀,文章持續更新;專注Java原始碼、架構、演算法和麵試。