由于項(xiàng)目上的需要,我要同時(shí)往orcale數(shù)據(jù)庫(kù)與sqlserver數(shù)據(jù)中插入數(shù)據(jù)妄田,需要在一個(gè)事務(wù)之內(nèi)完成這兩個(gè)庫(kù)的提交拄氯。參考了一下網(wǎng)上的各種JTA(Java Transaction API)實(shí)現(xiàn)之后啄育,選擇了Atomikos的實(shí)現(xiàn)该窗。
因?yàn)楫?dāng)時(shí)使用的時(shí)候繞的彎路大了點(diǎn),所以寫篇文章記錄下基本的實(shí)現(xiàn)過程蚤霞,方便日后查看酗失。如果是第一次使用,強(qiáng)烈建議去Atomikos查看官方例子與指導(dǎo)昧绣,寫的很詳細(xì)规肴。
前提
----XA是啥?
XA是由X/Open組織提出的分布式事務(wù)的架構(gòu)(或者叫協(xié)議)夜畴。XA架構(gòu)主要定義了(全局)事務(wù)管理器(Transaction Manager)和(局部)資源管理器(Resource Manager)之間的接口拖刃。XA接口是雙向的系統(tǒng)接口,在事務(wù)管理器(Transaction Manager)以及一個(gè)或多個(gè)資源管理器(Resource Manager)之間形成通信橋梁贪绘。也就是說兑牡,在基于XA的一個(gè)事務(wù)中,我們可以針對(duì)多個(gè)資源進(jìn)行事務(wù)管理税灌,例如一個(gè)系統(tǒng)訪問多個(gè)數(shù)據(jù)庫(kù)均函,或即訪問數(shù)據(jù)庫(kù)、又訪問像消息中間件這樣的資源菱涤。這樣我們就能夠?qū)崿F(xiàn)在多個(gè)數(shù)據(jù)庫(kù)和消息中間件直接實(shí)現(xiàn)全部提交边酒、或全部取消的事務(wù)。XA規(guī)范不是java的規(guī)范狸窘,而是一種通用的規(guī)范,
目前各種數(shù)據(jù)庫(kù)坯认、以及很多消息中間件都支持XA規(guī)范翻擒。
JTA是滿足XA規(guī)范的、用于Java開發(fā)的規(guī)范牛哺。所以陋气,當(dāng)我們說,使用JTA實(shí)現(xiàn)分布式事務(wù)的時(shí)候引润,其實(shí)就是說巩趁,使用JTA規(guī)范,實(shí)現(xiàn)系統(tǒng)內(nèi)多個(gè)數(shù)據(jù)庫(kù)淳附、消息中間件等資源的事務(wù)议慰。
JTA(Java Transaction API),是J2EE的編程接口規(guī)范奴曙,它是XA協(xié)議的JAVA實(shí)現(xiàn)别凹。它主要定義了:
- 一個(gè)事務(wù)管理器的接口
javax.transaction.TransactionManager
,定義了有關(guān)事務(wù)的開始洽糟、提交炉菲、撤回等>操作堕战。- 一個(gè)滿足XA規(guī)范的資源定義接口
javax.transaction.xa.XAResource
,一種資源如果要支持JTA事務(wù)拍霜,就需要讓它的資源實(shí)現(xiàn)該XAResource
接口嘱丢,并實(shí)現(xiàn)該接口定義的兩階段提交相關(guān)的接口。
如果我們有一個(gè)應(yīng)用祠饺,它使用JTA接口實(shí)現(xiàn)事務(wù)越驻,應(yīng)用在運(yùn)行的時(shí)候,就需要一個(gè)實(shí)現(xiàn)JTA的容器吠裆,一般情況下伐谈,這是一個(gè)J2EE容器,像JBoss试疙,Websphere等應(yīng)用服務(wù)器诵棵。但是,也有一些獨(dú)立的框架實(shí)現(xiàn)了JTA祝旷,例如Atomikos, bitronix都提供了jar包方式的JTA實(shí)現(xiàn)框架履澳。這樣我們就能夠在Tomcat或者Jetty之類的服務(wù)器上運(yùn)行使用JTA實(shí)現(xiàn)事務(wù)的應(yīng)用系統(tǒng)。
在上面的本地事務(wù)和外部事務(wù)的區(qū)別中說到怀跛,JTA事務(wù)是外部事務(wù)距贷,可以用來實(shí)現(xiàn)對(duì)多個(gè)資源的事務(wù)性。它正是通過每個(gè)資源實(shí)現(xiàn)的XAResource
來進(jìn)行兩階段提交的控制吻谋。感興趣的同學(xué)可以看看這個(gè)接口的方法忠蝗,除了commit, rollback等方法以外,還有end()
,forget()
,isSameRM()
,prepare()
等等漓拾。光從這些接口就能夠想象JTA在實(shí)現(xiàn)兩階段事務(wù)的復(fù)雜性阁最。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -------------- REST微服務(wù)的分布式事務(wù)實(shí)現(xiàn)-分布式系統(tǒng)、事務(wù)以及JTA介紹
如果還想了解更多的關(guān)于分布式事務(wù)的實(shí)現(xiàn)方式的可以看一個(gè)這個(gè)骇两,里面寫了7種思路速种。Spring的分布式事務(wù)實(shí)現(xiàn)-使用和不使用XA(翻譯)
環(huán)境: spring-boot 2.x + maven + atomikos + orcale + sqlserver + mybatis
maven:pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>con.demo</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.github.noraui</groupId>
<artifactId>ojdbc7</artifactId>
<version>12.1.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>6.4.0.jre8</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
</project>
application.properties里配置3個(gè)數(shù)據(jù)源
server.port=8085
logging.file=logs\\msgexchange.log
logging.level.root=INFO
logging.level.org.springframework.web=INFO
logging.level.cn.gov.customs.msgexchange=DEBUG
mybatis.check-config-location=true
mybatis.config-locations=classpath:mybatis-config.xml
#primary datasource
spring.datasource.driverClassName = oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@ip:port:dbname
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.initialSize=5
spring.datasource.minIdle=10
spring.datasource.maxActive=30
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#XADataSource orcale 數(shù)據(jù)源
spring.datasource.msg.xaDataSourceClassName=oracle.jdbc.xa.client.OracleXADataSource
spring.datasource.msg.url=jdbc:oracle:thin:@ip:port:dbname
spring.datasource.msg.user=user
spring.datasource.msg.password=password
spring.datasource.msg.uniqueResourceName=OracleXADataSource
spring.datasource.msg.initialSize=5
spring.datasource.msg.minIdle=10
spring.datasource.msg.maxActive=30
spring.datasource.msg.maxWait=60000
spring.datasource.msg.timeBetweenEvictionRunsMillis=60000
spring.datasource.msg.minEvictableIdleTimeMillis=300000
spring.datasource.msg.validationQuery=SELECT 1 FROM DUAL
spring.datasource.msg.testWhileIdle=true
spring.datasource.msg.testOnBorrow=false
spring.datasource.msg.testOnReturn=false
spring.datasource.msg.poolPreparedStatements=true
spring.datasource.msg.maxPoolPreparedStatementPerConnectionSize=20
#sqlserver 數(shù)據(jù)源
spring.datasource.dps.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.dps.url=jdbc:sqlserver://localhost:port;database=dbname
spring.datasource.dps.username=username
spring.datasource.dps.password=password
spring.datasource.dps.initialSize=5
spring.datasource.dps.minIdle=10
spring.datasource.dps.maxActive=30
spring.datasource.dps.maxWait=60000
spring.datasource.dps.timeBetweenEvictionRunsMillis=60000
spring.datasource.dps.minEvictableIdleTimeMillis=300000
spring.datasource.dps.validationQuery=SELECT 1 FROM DUAL
spring.datasource.dps.testWhileIdle=true
spring.datasource.dps.testOnBorrow=false
spring.datasource.dps.testOnReturn=false
spring.datasource.dps.poolPreparedStatements=true
spring.datasource.dps.maxPoolPreparedStatementPerConnectionSize=20
這里很尷尬,配置了這么多低千,由于使用的并不是springboot-start里的自動(dòng)裝配配阵。我又沒有手動(dòng)裝配,所有其實(shí)都沒有用上示血。棋傍。其中主數(shù)據(jù)源是平時(shí)單庫(kù)操作用的,他不需要使用分布式事務(wù)难审。
我使用了mybatis舍沙,所以現(xiàn)在來寫一下配置文件。
主數(shù)據(jù)源
@Configuration
@MapperScan(basePackages = {"com.demo.orcale.dao"}, sqlSessionTemplateRef = "SqlSessionTemplate")
public class MybatisConfig {
@Bean(name = "MybatisDS")
@ConfigurationProperties(prefix = "spring.datasource")
@Primary
public DataSource DataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "SqlSessionFactory")
@Primary
public SqlSessionFactory SqlSessionFactory(@Qualifier("MybatisDS") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Primary
@Bean(name = "TransactionManager")
public DataSourceTransactionManager TransactionManager(@Qualifier("MybatisDS") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Primary
@Bean(name = "SqlSessionTemplate")
public SqlSessionTemplate SqlSessionTemplate(@Qualifier("SqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
這里是配置了一下單庫(kù)操作的主數(shù)據(jù)源剔宪,這個(gè)和分布式事務(wù)沒有一點(diǎn)關(guān)系拂铡,為了平時(shí)數(shù)據(jù)庫(kù)的增刪改查使用壹无。
sqlserver數(shù)據(jù)源
@Configuration
@MapperScan(basePackages = ""com.demo.xa.sqlserver.dao", sqlSessionTemplateRef = "DpsSqlSessionTemplate")
@ConfigurationProperties(prefix = "spring.datasource.dps")
public class DPSMybatisConfig {
private String url;
private String username;
private String password;
@Bean(name = "DpsMybatisDS")
public DataSource DataSource() {
SQLServerXADataSource xaDataSource = new SQLServerXADataSource();
xaDataSource.setURL(url);
xaDataSource.setUser(username);
xaDataSource.setPassword(password);
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaDataSource(xaDataSource);
return ds;
}
@Bean(name = "DpsSqlSessionFactory")
public SqlSessionFactory SqlSessionFactory(@Qualifier("DpsMybatisDS") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean(name = "DpsSqlSessionTemplate")
public SqlSessionTemplate SqlSessionTemplate(@Qualifier("DpsSqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
orcale數(shù)據(jù)源
@Configuration
@MapperScan(basePackages = {"com.demo.xa.orcale.dao"}, sqlSessionTemplateRef = "SqlSessionTemplate")
public class MybatisConfig {
//從配置文件里注入吧
@Bean(name = "MybatisDS")
public DataSource DataSource() {
Properties properties = new Properties();
properties.setProperty("URL", "jdbc:oracle:thin:@172.18.11.62:1521:test");
properties.setProperty("user", "user");
properties.setProperty("password", "password");
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaProperties(properties);
ds.setUniqueResourceName("OracleXADataSource");
ds.setXaDataSourceClassName("oracle.jdbc.xa.client.OracleXADataSource");
return ds;
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory SqlSessionFactory(@Qualifier("MybatisDS") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean(name = "SqlSessionTemplate")
public SqlSessionTemplate SqlSessionTemplate(@Qualifier("SqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
使用XA的時(shí)候只需要把數(shù)據(jù)源配置成對(duì)應(yīng)數(shù)據(jù)庫(kù)的XA數(shù)據(jù)源就行了,其他的配置不用改
mysql就把數(shù)據(jù)源換成MysqlXADataSource完全沒區(qū)別。
定義個(gè)獨(dú)立的事務(wù)管理器,spring會(huì)為你管理的屯碴。(很遺憾沒有研究一下spring是如何接手事務(wù)管理的)
@Bean(name = "xatx")
@Primary
public JtaTransactionManager regTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
至此配置就配置完成了精堕。然后看一下如何使用
@Transactional(transactionManager = "xatx")
@GetMapping("/test")
public String test(){
//sqlservice
User user = new User();
user.setNo(UUID.randomUUID().toString().substring(10));
user.setName("bob");
userMapper.insert(user);
if (true){
throw new RuntimeException("this is my RunTime error");
}
//orcale
Car car = new Car();
car.setNo(UUID.randomUUID().toString().substring(10));
car.setName("sherry");
carMapper.insert(car);
return "操作成功";
}
在事務(wù)中拋出任意的RuntimeException的時(shí)候都會(huì)觸發(fā)事務(wù)的回滾蟹漓,要不兩個(gè)數(shù)據(jù)庫(kù)都提交,否則就都不提交。
其實(shí)搭建分布式事務(wù)的難點(diǎn)并不是在配置mybatis數(shù)據(jù)源這里。而是在配置數(shù)據(jù)庫(kù)上豺撑,orcale的還好說,只要給一下orcale賬戶的權(quán)限就完事黔牵,但是sqlserver數(shù)據(jù)庫(kù)的配置簡(jiǎn)直了聪轿,老老實(shí)實(shí)跟著官網(wǎng)步驟走吧。
atomikos官網(wǎng)步驟: Configuring Microsoft SQL Server for XA
微軟官網(wǎng):Understanding XA Transactions
上面這兩個(gè)才是最坑的猾浦。這里舉兩個(gè)我碰到的坑
問題1:沒有存儲(chǔ)過程xxx
這個(gè)問題基本看看是orcale還是sqlserver陆错,如果是orcale的就是沒有數(shù)據(jù)庫(kù)的表權(quán)限。找到官網(wǎng)的4個(gè)授權(quán)語句加上金赦,問題可以解決
grant select on sys.dba_pending_transactions to <user name>;
grant select on sys.pending_trans$ to <user name>;
grant select on sys.dba_2pc_pending to <user name>;
grant execute on sys.dbms_system to <user name>;
如果是sqlserver數(shù)據(jù)源報(bào)出的問題音瓷,那么找到驅(qū)動(dòng)中的xa_install.sql文件,運(yùn)行一下夹抗,他會(huì)建立很多觸發(fā)器與表绳慎,還會(huì)建立一個(gè)數(shù)據(jù)庫(kù)角色,你需要把sqlserver的登錄用戶賦予這個(gè)新建的角色漠烧。
問題2:函數(shù) RECOVER: 失敗杏愤。狀態(tài)為: -3。錯(cuò)誤:“*** SQLJDBC_XA DTC_ERROR Context: xa_recover, state=1
javax.transaction.xa.XAException: 函數(shù) RECOVER: 失敗沽甥。狀態(tài)為: -3。錯(cuò)誤:“*** SQLJDBC_XA DTC_ERROR Context: xa_recover, state=1, StatusCode:-3 (0xFFFFFFFD) ***”
at com.microsoft.sqlserver.jdbc.SQLServerXAResource.DTC_XA_Interface(SQLServerXAResource.java:550) ~[sqljdbc4-4.0.jar:na]
at com.microsoft.sqlserver.jdbc.SQLServerXAResource.recover(SQLServerXAResource.java:728) ~[sqljdbc4-4.0.jar:na]
at com.atomikos.datasource.xa.XATransactionalResource.recoverXidsFromXAResource(XATransactionalResource.java:554) [transactions-jta-3.9.3.jar:na]
at com.atomikos.datasource.xa.XATransactionalResource.recover(XATransactionalResource.java:512) [transactions-jta-3.9.3.jar:na]
at com.atomikos.datasource.xa.XATransactionalResource.recoverXidsFromResourceIfNecessary(XATransactionalResource.java:615)
............................
2018-08-08 09:11:33.221 WARN 2020 --- [ main] c.a.icatch.imp.TransactionServiceImp : ERROR IN RECOVERY
com.atomikos.datasource.ResourceException: Error in recovery
at com.atomikos.datasource.xa.XATransactionalResource.recoverXidsFromXAResource(XATransactionalResource.java:565) [transactions-jta-3.9.3.jar:na]
at com.atomikos.datasource.xa.XATransactionalResource.recover(XATransactionalResource.java:512) [transactions-jta-3.9.3.jar:na]
at com.atomikos.datasource.xa.XATransactionalResource.recoverXidsFromResourceIfNecessary(XATransactionalResource.java:615) [transactions-jta-3.9.3.jar:na]
at com.atomikos.datasource.xa.XATransactionalResource.endRecovery(XATransactionalResource.java:583) [transactions-jta-3.9.3.jar:na]
at com.atomikos.icatch.imp.TransactionServiceImp.recover(TransactionServiceImp.java:558) [transactions-3.9.3.jar:na]
at com.atomikos.datasource.xa.XATransactionalResource.setRecoveryService(XATransactionalResource.java:435) [transactions-jta-3.9.3.jar:na]
at com.atomikos.icatch.system.Configuration.installRecoveryService(Configuration.java:260) [transactions-3.9.3.jar:na]
at com.atomikos.icatch.imp.TransactionServiceImp.prepareConfigurationForPresumedAbortIfNecessary(TransactionServiceImp.java:581)
這個(gè)錯(cuò)誤折騰了很久乏奥。原因基本就是程序使用的sqlserver連接驅(qū)動(dòng)版本與數(shù)據(jù)庫(kù)dll的版本不一致摆舟,嘗試以下步驟:
1.檢查pom文件
mssql-jdbc版本,我這里是6.4.0.jre8邓了。那么就去微軟官網(wǎng)下同版本驅(qū)動(dòng)恨诱,把驅(qū)動(dòng)里面的sqljdbc_xa.dll丟帶相應(yīng)文件夾下,這個(gè)的詳細(xì)操作網(wǎng)上很多骗炉。
2.sqljdbc_auth.dll
把驅(qū)動(dòng)中的sqljdbc_auth.dll丟到C:\Windows\System32下照宝,然后重啟sqlserver服務(wù)。
3.都沒用..
是的句葵,我試了一下都沒用厕鹃,在這里卡了好幾個(gè)小時(shí)兢仰,但是我的直覺告訴我還是版本問題,所以我又去看了一眼pom文件剂碴。然后我發(fā)現(xiàn)了sqljdbc4這個(gè)包把将,那么mssql-jdbc包又是干嘛的,都是sqlserver的jdbc驅(qū)動(dòng)嗎忆矛,是不是重復(fù)了察蹲?然后我把sqljdbc4包刪了,世界恢復(fù)了正常催训,程序不報(bào)異常了洽议。這兩個(gè)應(yīng)該都是sqlserver的jdbc驅(qū)動(dòng),我也沒找到區(qū)別在哪漫拭,但是官網(wǎng)頁面使用的是mssql-jdbc的驅(qū)動(dòng)亚兄,保持一致就好了。
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>sqljdbc4</artifactId>
<version>4.0</version>
</dependency>
以上就是我用XA遇到的一些問題嫂侍,最坑的就是sqlserver數(shù)據(jù)庫(kù)這一塊的配置了儿捧。但是根本原因還是自己用之前沒有好好的讀一遍官網(wǎng)的使用說明文檔,白白的浪費(fèi)了時(shí)間挑宠。教訓(xùn)就是用之前有官網(wǎng)就去官網(wǎng)菲盾,官網(wǎng)有例子就改例子,有文檔就先讀文檔各淀,別去百度里瞎搜懒鉴。
結(jié)束語
分布式事務(wù)或者說,就分布式碎浇、微服務(wù)架構(gòu)的系統(tǒng)如何保證數(shù)據(jù)的一致性這一點(diǎn)來考慮临谱,當(dāng)然這是一個(gè)很大的話題,完全應(yīng)該獨(dú)立一篇文章出來討論奴璃。在MQ中悉默,你可以選擇使用回執(zhí)或者事務(wù)保持一致性。在SpringCloud你可以使用Hystrix的fallback或者TCC(Try-Confirm-Cancel)模式苟穆。又或者說存儲(chǔ)每一次的數(shù)據(jù)操作或者說儲(chǔ)存沒一個(gè)事件抄课,那么當(dāng)發(fā)生異常的時(shí)候我們能夠根據(jù)歷史事件重新生成數(shù)據(jù)的事件溯源(Event Sourcing)模式。等等這些都是用非事務(wù)的方式來確保數(shù)據(jù)一致雳旅。
附錄
微軟官網(wǎng):Understanding XA Transactions
atomikos: Configuring TransactionsEssentials?
感謝博客的分析文章:
codin:codin
微服務(wù)架構(gòu)下處理分布式事務(wù)跟磨,你必須知道的事兒
如何保障微服務(wù)架構(gòu)下的數(shù)據(jù)一致性?
從銀行轉(zhuǎn)賬失敗到分布式事務(wù):總結(jié)與思考