一、分布式事务产生的背景

在微服务环境下,会根据不同的业务会拆分成不同的服务,每个服务都有自己独立的数据库,服务与服务之间采用RPC远程调用进行通信,但在每个服务中都有自己独立的本地事务。当服务相互通讯的时候,两个本地事务互不影响,从而需要分布式事务。

二、解决分布式事务基本思路

ACID

关系型数据库天生就是解决具有复杂事务场景的问题,关系型数据库完全满足ACID的特性。
数据库管理系统中事务的四个特性

  1. 原子性(Atomicity)
  2. 一致性(Consistency)
  3. 隔离性(Isolation)
  4. 持久性(Durability)

所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。(执行单个逻辑功能的一组指令或操作称为事务)

Base(碱)

BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的简写,由 eBay 架构师 Dan Pritchett 于 2008 年在《BASE: An Acid Alternative》(论文地址点 这里)论文中首次提出。BASE 思想与 ACID 原理截然不同,它满足 CAP 原理,通过牺牲强一致性获得可用性, 一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。
BASE 模型包含如下三个元素:

  1. BA:(Basically Available ),基本可用。
  2. S:( Soft State),软状态,状态可以在一段时间内不同步。
  3. E:(Eventually Consistent ),最终一致,在一定的时间窗口内, 最终数据达成一致即可。

关于最终一致的几种变种参见上面,在实际系统实践中,可以将若干变种结合起来,来实现各种业务需求。

CAP(帽子 )

由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统,针对分布式系统的CAP原理包含如下三个元素。

  1. C:Consistency,致性。在分布式系统中的所有数据 备份,在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本。
  2. A:Availability,可用性,好的响应性能。完全的可用性指的是在任何故障模型下,服务都会在有限的时间内处理完成并进行响应。
  3. P: Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可继续工作。

CAP原理证明,任何分布式系统只可同时满足以上两点,无法三者兼顾。由于关系型数据库是单节点无复制的,因此不具有分区容忍性,但是具有一致性和可用性,而分布式的服务化系统都需要满足分区容忍性,那么我们必须在一致性和可用性之间进行权衡。如果在网络上有消息丢失,也就是出现了网络分区,则复制操作可能会被延后,如果这时我们的使用方等待复制完成再返回,则可能导致在有限时间内无法返回,就失去了可用性:而如果使用方不等待复制完成,而在主分片写完后直接返回,则具有了可用性,但是失去了一致性。

三、柔性事务和刚性事务

  1. 柔性事务满足BASE理论(基本可用,最终一致)
  2. 刚性事务满足ACID理论

四、分布式事务常见解决方案

XA接口

XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。XA之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲(参考Fischer等的论文),两台机器理论上无法达到一致的状态,需要引入一个单点进行协调。事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或JMS队列)

Jta规范

作为java平台上事务规范JTA(Java Transaction API)也定义了对XA事务的支持,实际上,JTA是基于XA架构上建模的,在JTA 中,事务管理器抽象为javax.transaction.TransactionManager接口,并通过底层事务服务(即JTS)实现。像很多其他的java规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要由以下几种:

  1. J2EE容器所提供的JTA实现(JBoss)
  2. 独立的JTA实现:如JOTM,Atomikos.这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用。

五、解决分布式事务

  1. 如果是传统单项目中,存在多数据源的情况下,可采用JTA+ Atomikos去管理数据源从而解决多数据源事物问题。
  2. 如果在分布式微服务项目中,服务与服务都是都过RPC调用的,则在设计时,可在一方面避免分布式事物的产生,比如现在有一个服务需要向数据库中插入一天数据,接着还要通知其他服务也要向数据库插入数据,则发起方可以将通知另一台服务的程序放在该方法的最后执行,等本地操作都完成再去通知远程服务,然后等待远程服务的结果,如果失败,则手动回滚本地事物,成功则提交。
  3. 如果在复杂的情况下,必须使用分布式事物,可以选择LCN解决分布式事物问题。

六、单机LCN TxManager

LCN的官网:github

https://github.com/codingapi/tx-lcn

这里使用tx-lcn-2.0

首先编译tx-lcn 的环境,用mvn 将tx-clinet、tx-lcn、tx-plugins install到自己maven环境中。

然后去tx-manage下修改配制文件中的Eureka的地址,和redis的地址。

如果这里有不明白SpringCloud微服务搭建的,可以去我博客找 “SpringCloud Eureka做服务治理和 Eureka注册中心集群搭建 及 自我保护机制” 这篇文档参考。

先启动eureka注册中心,再启动TxManager,启动成功后可再浏览器输入:

http://localhost:8899/

如果有如下效果则TxManager启动成功。

springcloud redission分布式锁实现 springcloud如何解决分布式事务_数据库


注意,这里LCN底层通信采用的端口为 9999,如果部署在CenterOS下,需要开放该端口。

七、SpringBoot 2.X集成LCN客户端

现在又两个数据库Db1,Db2,分别为服务提供者,和消费者使用。这里关于MybatisPlus数据库的配制和操作不进行展示了。

1. 服务提供者

POM

<!--        LCN-->
        <dependency>
            <groupId>com.codingapi</groupId>
            <artifactId>transaction-springcloud</artifactId>
            <version>4.1.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.codingapi</groupId>
            <artifactId>tx-plugins-db</artifactId>
            <version>4.1.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

application.properties

#LCN
tm.manager.url = http://127.0.0.1:8899/tx/manager/

重写Service

@Service
public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {

    @Value("${tm.manager.url}")
    private String url;

    @Override
    public String getTxUrl() {
        System.out.println("load tm.manager.url ");
        return url;
    }
}
@Service
public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService {

    @Override
    public String httpGet(String url) {
        System.out.println("httpGet-start");
        String res = HttpUtils.get(url);
        System.out.println("httpGet-end");
        return res;
    }

    @Override
    public String httpPost(String url, String params) {
        System.out.println("httpPost-start");
        String res = HttpUtils.post(url, params);
        System.out.println("httpPost-end");
        return res;
    }
}

使用

在需要使用分布式事物的方法上添加注解

@TxTransaction(isStart = false)

其中isStart =true的话代表发起方,false代表接收方。

2. 服务消费者

和服务提供者配制一样,在调用服务时,注解的isStart设为true:

@TxTransaction(isStart = true)