分布式事务Atomikos介绍与实践

分布式事务
placeholder image
admin 发布于:2023-02-03 16:44:01
阅读:loading

1.基本介绍

Spring Boot整合了Atomikos和Bitronix两个JTA(Java Transaction API)的实现,但在2.3.0的版本中Bitronix不推荐使用Bitronix了,所以重点来关注一下Atomikos的组件。Atomikos提供了两款分布式事务产品级解决方案,分别是ExtremeTransactions和TransactionsEssentials,前者需要商业授权,后者是开源免费使用版本,TransactionsEssentials是免费的,可无缝升级为ExtremeTransactions版本,选择官方说是否商用版本取决于您是否认真对待事务,并希望组成自己的轻量级运行时以取代传统的应用程序服务器,反正对于个人来讲闭着眼睛肯定是开源免费版本。

Atomikos TransactionsEssentials是领先的开源JTA/XA分布式事务管理实现,使用JTA/XA和连接池,可用于应用服务器之外的自包含应用程序,它是一个无需应用服务器的事务管理器,比同类产品更容易使用和更成熟,关于为什么要使用Atomikos以及它的特性,可查看官网的更加详细说明:https://www.atomikos.com/Documentation/WhyUseAtomikos。假设在两台不同的机器上有两个进程(A和B),如何确保跨越这两个流程的任务的可靠性?换句话说:你怎么能确保A没有做部分功能(而B没有)或者相反,根据需要的内容和应用程序的性质,有不同的方法来实现这一点。关于JTA Atomikos的更多理论知识自行去百科,本次基于jta-atomikos + Spring Boot + MyBatils Plus/Spring Data JDBC + MySQL 来实现两个数据库的事务管理,先来看下项目结构如下图所示:

image.png

2.示例说明

本次示例构建在一个多模块的项目下,两个业务功能模块分别对应了有两个不同的数据源,演示在不同数据源下利用用数据库持久层框架实现的事务一致性,同时提交或回滚。

2.1 项目结构

(1)jta-atomikos是整个项目名称,起到定义使用框架版本信息和具体子模块的功能;

(2)jta-base是所有业务功能的子模块,定义各个模块的公用组件依赖和公共处理类;

(3)jta-bootstrap是项目的主启动模块,定义Spring Boot程序的启动;

(4)jta-users是业务功能模块,定义用户相关的模块功能;

(5)jta-bless是业务功能模块,随便定义另外的一块业务功能(由于实践阶段是过年正当时,起名bless寓意祝福和好运吧);

2.2 项目模块

(1)jta-base独立,不依赖项目其它模块,参考maven pom.xml依赖(此处仅包含jta-base模块的公共依赖,springboot依赖为2.2.7.RELEASE版本)如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.2.7.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
        <version>2.2.7.RELEASE</version>
    </dependency>

    <!-- 数据库相关 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        <version>2.2.7.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.2.7.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.16</version>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.1</version>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.6</version>
    </dependency>

</dependencies>

(2)jta-users和jta-bless均依赖jta-base;

(3)jta-bootstrap依赖jta-users和jta-bless两个模块;

2.3 项目实现

(1)包含多个数据源的定义(示例中数据源的参数只提供了最基本的4个常规必备参数,实际应用应该有更多的配置);

(2)使用XML配置管理Spring Bean组件的声明(特地再用XML配置的形式练习练习,毕竟很多场景下的XML配置优于Java代码);

(3)两个模块分别对应了两个不同机器的MySQL数据库,分别是本机127.0.0.1和本站的远程阿里云服务器上的MySQL,两个MySQL版本分别为5.7.26、5.7.33;

(4)示例中包含4个验证,分别是两个数据源使用MyBatis Plus Api的事务提交、两个数据库的使用MyBatis Plus Api的事务回滚;两个数据库使用MyBatis Plus Api和Spring Data Jdbc Api的事务回滚;

3.示例代码

image.png

package cn.chendd.transactional.service.impl;

import cn.chendd.bless.mapper.GoodLuckMapper;
import cn.chendd.bless.model.GoodLuck;
import cn.chendd.transactional.service.TransactionalService;
import cn.chendd.users.mapper.UsersMapper;
import cn.chendd.users.model.Users;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * 验证事务接口实现
 *
 * @author chendd
 * @date 2023/1/20 16:29
 */
@Service
public class TransactionalServiceImpl implements TransactionalService {

    /**
     * 本机数据源
     */
    @Resource
    private UsersMapper usersMapper;
    /**
     * 远程数据源
     */
    @Resource
    private GoodLuckMapper goodLuckMapper;
    @Resource(name = "blessNamedParameterJdbcTemplate")
    private NamedParameterJdbcTemplate blessJdbcTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveData() {
        //构造用户数据对象并保存
        Users user = new Users(null , "chendd" , "男" , "88911006@qq.com" , LocalDateTime.now().toString());
        this.usersMapper.insert(user);
        //构造好运数据对象并保存
        GoodLuck goodLuck = new GoodLuck(null , "chendd" , "春节快乐!");
        this.goodLuckMapper.insert(goodLuck);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveDataMyBatisPlus() {
        this.saveData();
        System.out.println("抛出异常,事务回滚...");
        throwException();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveDataMyBatisPlusOrJdbcTemplate() {
        //构造用户数据对象并保存
        Users user = new Users(null , "chendd" , "男" , "88911006@qq.com" , LocalDateTime.now().toString());
        this.usersMapper.insert(user);
        //构造好运数据对象并保存
        GoodLuck goodLuck = new GoodLuck(IdWorker.getId(), "chendd" , "春节快乐!");
        String sql = "insert into test(id , name , remark) values (:id , :name , :remark)";
        this.blessJdbcTemplate.batchUpdate(sql , SqlParameterSourceUtils.createBatch(goodLuck));
        System.out.println("抛出异常,事务回滚...");
        throwException();
    }

    @Override
    public Users queryUsers() {
        return this.usersMapper.queryUsers();
    }

    /**
     * 抛出运行时异常
     */
    private void throwException() {
        System.out.println(1 / 0);
    }

}

以上是Service接口的实现定义,UserMapper是本机127.0.0.1的MySQL数据源,GoodLuckMapper和blessJdbcTemplate是本站阿里云数据库服务器的数据源;

4.运行效果

jta-atomikos.gif

考虑文章的图片演示大小,录制的运行效果图比较简陋,大概只能看到发送了三次请求,其中每个请求都向不在同一台机器上的两个数据库表中插入数据,成功一次,失败两次,即数据库表中成功提交一次,各一条数据。特专门录制了一个更为详细的录屏示例过程,从服务器启动开始,一步一步的演示,一步一步的说明,值得观看,参考下方的源码下载。

5.其它说明

(1)工程实例项目源码见:相关下载.zip

(2)Atomikos事务集成的在服务器启动时的提示警告:“Thanks for using Atomikos!”,类似重要的事情说三次,参考如下图所示:

image.png

经过对源代码的分析和验证在resources目录下增加transactions.properties文件,并设置com.atomikos.icatch.registered = true参数,可起到注册atomikos的目的来屏蔽这段日志输出,参考如下图所示:

image.png


 点赞


 发表评论

当前回复:作者

 评论列表


留言区