1

前言

分布式事务拆开来其实就是分布式、事务两个概念,分布式简单讲就是不同进程间的系统进行通信;事务狭义上我们经常把它看作是数据库的事务,事务具有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源码、架构、算法和面试。

ksfzhaohui
398 声望70 粉丝