Finally someone put a distributed transaction that clear!

Foreword

This article will introduce you some insight on what distributed transactions, and explain the implementation of the principle of distributed transaction processing framework TX-LCN and wrong with the hope you feel free to correct me.

v2-bb348a5cd5ad0d7634636aa3c1543c62_hd.png

1. The need to use distributed transactions under what circumstances?

Many scenes used to give a common: the micro-services system, if a business need to use a different micro-services, and different corresponding to different micro-services database.

Analogy: electricity supplier platform business logic orders for the next customer, the business logic involves two micro-services, a service inventory (inventory minus one), the other is the order service (plus a number of orders), diagram below :

v2-e67e8d42cc14f0cd06143e8c8a452c2b_hd.jpg

If you do not use distributed transactions when executing this business logic, inventory and order when one fails, it is likely such a situation: the value of inventory decreased by 1 database, but the database does not change the order; or no change in inventories , more than an order, that is, the emergence of data inconsistency.

So in a similar situation we want to use distributed transactions to ensure data consistency.

2. Solutions for distributed transactions

2.1 introduces: MySQL in the two-phase commit strategy

Before talking Solutions distributed transaction, we take a look at how a single data source is doing transaction processing, we can derive some inspiration.

Our MySQL's InnoDB engine, for example, because there are two sets of MySQL logging mechanism is a redo log storage layer, the other a binlog server layers, each update data is updated every two logs. In order to prevent wrote only one log write and not write another, MySQL uses a called two-phase commit way to ensure transactional consistency. Specifically this:

Suppose create such a database: mysql> create table T (ID int primary key, c int) ;, then execute such an update statement: mysql> update T set c = c + 1 where ID = 2;

This update statement execution process is like this:

  1. First performed to find the engine will take the line data of ID = 2

  2. +1 data will get the data, and then calls the new data is written to the interface engine

  3. 引擎将数据更新到内存中,并将操作记录到 redo log 里,此时 redo log 处于 prepare 状态。但它不会提交事务,只是通知执行器已经完成任务,可以随时提交。

  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘

  5. 最后执行器调用引擎的事务接口,把 redo log 改为提交状态,更新完成。

在上述过程中,redo log 写完后没有直接提交,而是处于 prepare 状态,等通知执行器并把 binlog 写完后,redo log 再进行提交。这个过程就是两阶段提交,这是一个精妙的设计。

可能你会问为什么要有两阶段提交?如果不采用两阶段提交的话,也就是使用一阶段提交,那就相当于按顺序执行写 redo log 和 binlog,如果写完 redo log 后系统出现了故障,那么就会只有 redo log 记录了操作,binlog 没有记录,造成数据不一致;使用两阶段提交的话,假设写完 redo log 后系统出现了故障,由于事务还没有提交,所以可以顺利回滚。

两阶段提交的设计还有什么好处?首先要奠定一个概念:一个操作执行的时间越长,这个操作就越有可能失败。打个比方,你吃饭要用 20 分钟,上厕所要用 1 分钟,在吃饭的过程中收到微信消息的概率肯定比去上厕所的过程中收到微信消息的概率大。由于在数据库中更新操作的时间要远大于提交事务的时间,所以先把更新操作做完,等所有耗时操作都做完最后再提交事务,能够最大程度保证事务执行成功。

2.2分布式事务的两阶段提交策略

根据上述的两阶段提交策略,分布式事务也可以采取类似的办法完成事务。

在第一阶段,我们要新增一个事务管理者的角色,通过它来协调各个数据源。还是拿开头的订单案例讲解,在执行下订单的逻辑时,先让各个数据库去执行各自的事务,比如从库存中减 1,在订单库中加 1,但是完成后不提交,只是通知事务管理者已经完成了任务。

v2-f83fb65d814695261e366a30a610b5b3_hd.jpg

到了第二阶段,由于在阶段一我们已经收到了各个数据源是否就绪的信息,只要有一个数据源没有就绪,在第二阶段就通知所有数据源回滚;如果全部数据源都已经就绪,就通知所有数据源提交事务。

v2-f5b41cd94b91a064934b746a48745ec4_hd.jpg

总结一下这个两阶段提交的过程就是:首先事务管理器通知各个数据源进行操作,并返回是否准备好的信息。等所有数据源都准备好后,再统一发送事务提交(回滚)的通知让各个数据源提交事务。由于最后的提交操作耗时极短,所以操作失败的可能性会很低。

那么这个两阶段提交协议可能存在什么缺点呢?很可能存在被阻塞的问题,假如其中一个数据源出现了某些问题阻塞了,既不能返回成功信息,也不能返回失败信息,那么整个事务将被阻塞。对应的策略是添加一些倒计时的操作,或者是重新发送消息。

欢迎大家关注我的公种浩【程序员追风】,文章都会在里面更新,整理的资料也会放在里面。

3. 分布式事务框架 TX-LCN

讲了这么多理论的知识,下面讲解一款真正应用在生产中的分布式事务框架 TX-LCN 的运行原理。(典型的分布式事务框架不止 TX-LCN,比如还有阿里的 GTS,不过 GTS 是收费的,TX-LCN 是开源的)

我们先看一下官方文档中给出的运行原理示意图:

v2-8c5aaa1b5c6546d13f581bfec4842787_hd.jpg

思路和我们上面讲的两阶段分布式事务处理流程差不多(有小不同),核心步骤分为 3 步:

  1. 创建事务组:在事务发起方开始执行业务代码之前先调用 TxManager 创建事务组对象,然后拿到事务表示 GroupId 的过程。简单来说就是对这次下订单的操作在事务管理中心里创建一个对象,拿到一个 id。

  2. 加入事务组:参与方在执行完业务方法后,将该模块的事务信息通知给 TxManager 的操作。也就是指各个数据源(各个服务)完成操作后,和事务管理中心说一声,注册一下自己。

  3. 通知事务组:发起方执行业务代码后,将发起方执行结果状态通知给 TxManager,TxManager 将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。和客户打交道的下订单服务会收到减库存和加订单是否成功消息,它会把这两个消息通知给事务管理者,事务管理者根据情况通知两个库存服务提交事务或回滚事务。

目前发现网上有一篇不错的 TX-LCN 执行源码分析文章: https://blog.csdn.net/cgj296645438/article/details/93860384

文章中跟着源码走一遍会发现和上面的流程图差不多,落实到代码中有一些精彩的地方,比如:

public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {
        if (Objects.isNull(DTXLocalContext.cur())) {
            DTXLocalContext.getOrNew();
        } else {
            return business.call();
        }
        log.debug("<---- TxLcn start ---->");
        DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
        TxContext txContext;
        // ---------- 保证每个模块在一个DTX下只会有一个TxContext ---------- //
        if (globalContext.hasTxContext()) {
            // 有事务上下文的获取父上下文
            txContext = globalContext.txContext();
            dtxLocalContext.setInGroup(true);
            log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
        } else {
            // 没有的开启本地事务上下文
            txContext = globalContext.startTx();
        }
        //......
}

这段代码保证了每个模块下只会有一个 TxContext,换个说法就是假设一个业务逻辑不是操作不同的数据源,而是对同一个数据源执行多次相同的操作,那么该数据源对应的模块在 DTX 下会只有一个 TxContext

v2-3726a67ba0cb29ed5e73be58847c1d7b_hd.png

LCN 的事务协调机制

LCN 的口号是:LCN 并不生产事务,LCN 只是本地事务的协调工。大家肯定会有个疑问,它不生产事务,那么它是怎么控制各个模块在完成事务的逻辑操作之后不马上提交,而是等到 TxManager 最后一起通知各模块提交的呢?

因为每个模块都是一个 TxClient,每个 TxClient 下都有一个连接池,是框架自定义的连接池,对 Connection 使用静态代理的方式进行包装。

public class LcnConnectionProxy implements Connection {
    private Connection connection;
    public LcnConnectionProxy(Connection connection) {
        this.connection = connection;
    }
    /**
     * notify connection
     *
     * @param state transactionState
     * @return RpcResponseState RpcResponseState
     */
    public RpcResponseState notify(int state) {
        try {
            if (state == 1) {
                log.debug("commit transaction type[lcn] proxy connection:{}.", this);
                connection.commit();
            } else {
                log.debug("rollback transaction type[lcn] proxy connection:{}.", this);
                connection.rollback();
            }
            connection.close();
            log.debug("transaction type[lcn] proxy connection:{} closed.", this);
            return RpcResponseState.success;
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
            return RpcResponseState.fail;
        }
    }
    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        connection.setAutoCommit(false);
    }
    //......
}

Connection pooling does not receive notice before the transaction will always occupy the connection resources distributed transactions. By the time the last TxManager notice TxClient, TxClient will go to perform the appropriate commit or rollback. So the LCN transaction coordination mechanism to intercept the equivalent of a little connection pool, the transaction commit control connection.

v2-8a7886b1ed99d649419b267b70e48af9_hd.png

LCN transaction compensation mechanism

Since we can not guarantee that every transaction executed properly, if at the time of the implementation of a business method should perform a successful operation but because the server-hook or network jitter and other issues cause the transaction not submitted properly, this scenario needs to be done by compensation affairs.

In this case TxManager will make a mark; then returned to the originator. He told The presence of affairs without notice to, and then TxClient execute the request transaction times again.

At last

Welcome to share with everyone, like the article I remember concern thumbs forward yo, thanks for the support!


Guess you like

Origin blog.51cto.com/14442094/2443425