10、如何解决分布式事务?

根据cap理论,分布式事务 acid 是很难保证的。一般做法是牺牲一致性,满足可用性和分区容错。
分布式事务可以基于 mq/rpc 实现最终一致性,但在分库分表,立即可见的应用上是不能满足业务需求的,故分布式事务还是有必要完善的。

BASE理论
在分布式系统中,我们往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给我们提出来了另外一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:

Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

分布式事务的基本介绍

分布式事务服务(Distributed Transaction Service,DTS)是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。CAP理论告诉我们在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的,所以我们只能在一致性和可用性之间进行权衡。为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性。数据一致性理解:

        强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。

        弱一致性:系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。

        最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。

        在分布式系统中各个节点在物理上都是相对独立的,独立每一台节点数据操作都可以满足ACID。但是,各独立节点之间无法知道其他节点事务的执行情况,如果要想让分多台机器中数据保存一致,就必须保证所有节点上面的数据操作要么全部执行成功,要么全部不执行,比较常规的解决方法是引入“协调者”来统一调度所有节点执行。

XA规范

        X/Open组织(即现在的Open Group)定义了分布式事务处理模型。X/Open DTP模型包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)、通信资源管理器(CRM)四部分。事务管理器(TM)是交易中间件,资源管理器(RM)是数据库,通信资源管理器(CRM)是消息中间件。通常把一个数据库内部的事务处理作为本地事务看待,而分布式事务处理的对象是全局事务。所谓全局事务,是指分布式事务处理环境中,多个数据库可能需要共同完成一个工作,这个工作即是一个全局事务,一个事务中可能更新几个不同的数据库,此时一个数据库对自己内部所做操作的提交不仅依赖本身操作是否成功,还要依赖与全局事务相关的其它数据库的操作是否成功,如果任一数据库的任一操作失败,则参与此事务的所有数据库所做的所有操作都必须回滚。XA就是X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等,XA接口函数由数据库厂商提供,根据这一思想衍生出二阶提交协议和三阶提交协议。

二阶段提交

所谓的两个阶段是指:准备阶段、提交阶段。

准备阶段:事务协调者(事务管理器)给每个参与者(资源管理器)发送准备消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志但不提交,可以进一步将准备阶段分为以下三个步骤:

1)协调者节点向所有参与者节点询问是否可以执行提交操作(do),并开始等待各参与者节点的响应。

2)参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。

3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。

提交阶段:如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息,否则发送提交(Commit)消息,参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。

二阶段提交所存在缺点的:

1)同步阻塞问题,执行过程中所有参与节点都是事务阻塞型的,当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

2)单点故障,由于协调者的重要性一旦协调者发生故障参与者会一直阻塞下去。

3)数据不一致
        在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求,而在这部分参与者接到commit请求之后就会执行commit操作,但是其他部分未接到commit请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据部一致性的现象。

由于二阶段提交存在着诸如同步阻塞、单点问题、数据不一致、宕机等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。

三阶段提交

三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

CanCommit阶段:3PC的CanCommit阶段其实和2PC的准备阶段很像,协调者向参与者发送do请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

个人觉得没有什么改进。。。根本上无法解决网络脑裂,数据不一致的情况。

Mycat中实现

Mycat可以通过用户会话Session中设置autocommit=false启动事务,通过设置ServerConnection中变量txInterrupted=true来控制是否事务异常需要回滚。在Mycat中的事务是一种二阶段提交事务方式,但是从实际应用场景出发这种出现故障的概率还是比较小的,因此这种实现方式可以满足很多应用需求,但如果出现问题,会很麻烦。

JTA

补偿事务(TCC)

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

1、Try 阶段主要是对业务系统做检测及资源预留
2、Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
3、Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

举个例子,假入 Bob 要向 Smith 转账100,思路大概是:
我们有一个本地方法,里面依次调用
1、首先在 Try 阶段,要先调用远程接口把 Smith 冻结金额+100和 Bob 冻结金额+100,可用金额-100。
2、在 Confirm 阶段,Smith解冻出账,Bob解冻加款。
3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻还原。

优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

本地消息表(异步确保)

本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:
基本思路就是:

消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。

消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

MQ 事务消息

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中间件为例,其思路大致为:

第一阶段Prepared消息,会拿到消息的地址。
第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要向消息队列提交两次请求,一次提交消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

这里写图片描述

优点: 实现了最终一致性,不需要依赖本地数据库事务。

缺点: RocketMQ事务消息部分代码也未开源。

二阶段基于数据库的实现:

关闭数据库自动提交。
set AUTOCOMMIT=0;

public class JdbcCommit {
    //创建xaid
    //执行
    //提交

    private Map<String ,Connection> map = new HashMap<>() ;
    DBConnect dbConnect = new DBConnect();

    public JdbcCommit() throws ClassNotFoundException {
    }

    public  String doInsert() throws Exception {
        String sql = "insert into USER(USERNAME,PASSWORD) values ('3','42')" ;
        String xaid =  new Random().nextInt(100)+21+"" ;

        Connection connection = dbConnect.getConnection();
        dbConnect.setAutoCommit(connection,false);
        int done  = dbConnect.preDoUpdate(connection,sql);
        if(done >0){
            dbConnect.doSelect(connection,"select * from USER");
        }
        map.put(xaid,connection);
        return  xaid ;
    }



    public  void commit(String xaid ) throws Exception {

        Connection connection = map.get(xaid) ;
        connection.rollback();
        //connection.commit();  //依靠传入的类型设定不同
        dbConnect.doSelect(connection,"select * from USER");
        map.remove(xaid);
        dbConnect.close(connection);
    }

    public static void main(String[] args) throws Exception {
        JdbcCommit jdbcCommit = new JdbcCommit();
        String xaid = jdbcCommit.doInsert();
        Thread.sleep(2000l);
        System.out.println("commit");
        jdbcCommit.commit(xaid);
    }
}

12 3 42
13 3 42
commit
12 3 42
发现13被回滚了 。

参考:https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值