虽然现在微服务越来越流行,我们的系统随之也拆分出来好多的模块功能。这样做的目的其实就是为了弥补单体架构中存在的不足。随着微服务的拆分,肯定设计到分库分表,但这之中肯定设计到分布式事务。最典型的例子就是银行转账,比如银行A给银行B转账500 块钱,流程肯定是银行A-500,银行B+500,在这个过程要么都成功,要么都成仁。首先银行A和银行B的数肯定是在不同的数据库,如果在转账的过程中,银行A首先-500库钱之后,在银行B+500的时候出现了问题,如果事务不回滚,那么就会出现500块钱丢失的问题,也就是出现了事务一致性问题。
JTA + Atomikos解决分布式事务
一、JTA
JTA(java Transaction API)是JavaEE 13 个开发规范之一。Java 事务API,允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。JDBC驱动程序的JTA支持极大地增强了数据访问能力。事务最简单最直接的目的就是保证数据的有效性,数据的一致性。
二、Atomikos
Atomikos是一个为Java平台提供增值服务的并且开源类事务管理器。
工作原理:分布式事务包括事务管理器和支持XA的资源管理器。资源管理器就是我们的DB,事务管理器就是承担调节和控制所有参与DB所设计到的事务。
个人理解:Atomikos 获取到数据库的连接之后,会屏蔽数据库底层的事务控制,然后全部交给 Atomikos,进行统一的调度和控制。
接下来,我们简单的做一个基于 SpringBoot 的分布式事务控制。
1、首先我们要引入需要引入的maven库
|
1
2
3
4
5
|
<
font
style
=
"color:rgb(77, 77, 77)"
>
<
font
face
=
"""
>
<
font
style
=
"font-size:16px"
>
<
!
--分布式事务-->
<
dependency
>
<
groupId
>
org.springframework.boot
<
/
groupId
>
<
artifactId
>
spring
-
boot
-
starter
-
jta
-
atomikos
<
/
artifactId
>
<
/
dependency
>
<
/
font
>
<
/
font
>
<
/
font
>
|
2、配置数据源

3、配置类

最主要的配置
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<
font
style
=
"color:rgb(77, 77, 77)"
>
<
font
face
=
"""
>
<
font
style
=
"font-size:16px"
>
@Configuration
@MapperScan
(
basePackages
=
"com.example.mapper.db1"
,
sqlSessionFactoryRef
=
"db1SqlSessionFactory"
)
public
class
DB
1
DataSourcesConfig
{
@Primary
@Bean
(
name
=
"db1DataSource"
)
public DataSource dataSource
(
DB
1
Config DB
1
Config
)
{
/
/
设置数据库连接
MysqlXADataSource mysqlXADataSource
=
new
MysqlXADataSource
(
)
;
mysqlXADataSource.setUrl
(
DB
1
Config.getUrl_jdbc
(
)
)
;
mysqlXADataSource.setUser
(
DB
1
Config.getUsername
(
)
)
;
mysqlXADataSource.setPassword
(
DB
1
Config.getPassword
(
)
)
;
mysqlXADataSource.setPinGlobalTxToPhysicalConnection
(
true
)
;
/
/
交给事务管理器进行管理
AtomikosDataSourceBean atomikosDataSourceBean
=
new
AtomikosDataSourceBean
(
)
;
atomikosDataSourceBean.setXaDataSource
(
mysqlXADataSource
)
;
atomikosDataSourceBean.setUniqueResourceName
(
"db1DataSource"
)
;
return
atomikosDataSourceBean;
}
@Primary
@Bean
(
name
=
"db1SqlSessionFactory"
)
public SqlSessionFactory sqlSessionFactory
(
@Qualifier
(
"db1DataSource"
)
DataSource dataSource
)
throws Exception
{
SqlSessionFactoryBean sessionFactoryBean
=
new
SqlSessionFactoryBean
(
)
;
sessionFactoryBean.setDataSource
(
dataSource
)
;
sessionFactoryBean.setMapperLocations
(
new
PathMatchingResourcePatternResolver
(
)
.getResources
(
"classpath*:/mapper/db1/*.xml"
)
)
;
org.apache.ibatis.session.Configuration
configuration
=
new
org.apache.ibatis.session.Configuration
(
)
;
configuration
.setMapUnderscoreToCamelCase
(
true
)
;
sessionFactoryBean.setConfiguration
(
configuration
)
;
return
sessionFactoryBean.getObject
(
)
;
}
@Primary
@Bean
(
name
=
"db1SqlSessionTemplate"
)
public SqlSessionTemplate sqlSessionTemplate
(
@Qualifier
(
"db1SqlSessionFactory"
)
SqlSessionFactory sqlSessionFactory
)
{
return
new
SqlSessionTemplate
(
sqlSessionFactory
)
;
}
}
<
/
font
>
<
/
font
>
<
/
font
>
|
这样基本就配置完了,这里我只写了一个数据库的配置,另外一个同这个一样,就是数据库信息变了而已。
4、测试
这里设计到两个数据的修改,学生姓名和学生分数成绩,这两个数据分别存储在不同的数据库。我们通过接口先修改用户信息,接着再修改用户的分数信息。
没有修改前的数据


我们先没不加事务,先看下如果再修改分数的时候出现异常,用户信息会不会回滚回去。

结果
首先代码在39行报错,如果按照我们的逻辑,如果修改分数失败,那么相应的用户的年龄修改也是不成功的,但是结果显示用户的年龄还是被修改了,显然这个接口并没有被分布式事务所管理。



我们先把数据改回原来,然后我们再把事务加上,看还不会出现这种问题。(方法上加@Transactional注解即可)

结果
加了分布式事务之后,通过结果就可以看到,如果后面的结果出现了错误,前面的数据也是会进行回滚的,保证了事务的前后一致性,确保了数据的安全准确。



总结:以上就是 JTA + Atomikos 实现分布式事务的整个过程,相对实现功能来说还是比较简单的,以上的测试是基于单个的 SpringBoot 项目的,相对于真正的微服务来说,这样的方式我还没有进行测试,如果需要进行微服务之间的事务管理,也可以通过 mq 或者 dubbo 去进行事务的一致性管理。等以后研究了再来跟大家分享。
