阿里开源分布式事务Fescar demo示例及原理分析-阿里云开发者社区

开发者社区> 数据库> 正文

阿里开源分布式事务Fescar demo示例及原理分析

简介: 分布式事务Fescar 使用及原理解析

@[toc]

简介

阿里巴巴近日开源了分布式事务中间件 fescar。GitHub地址是 https://github.com/alibaba/fescar
官方中文文档:https://github.com/alibaba/fescar/wiki/Home_Chinese
但是现在中文文档连接都不对,打不开,不知为何。

阿里巴巴现在内部使用的版本是GTS,fescar是其开源社区版本,fescar是Fast & EaSy Commit And Rollback, FESCAR的简称。更多简介请参考

==fescar架构简介参考: 阿里巴巴开源分布式事务解决方案 Fescar==

运行官方demo

https://github.com/alibaba/fescar克隆到本地IDEA,demo运行使用到的是examples和server。
image

我这里使用的是0.1.1版本:
image
运行程序首先需要配置数据库并且初始化SQL。
1、修改以下三个配置文件的MySQL地址
image

test module中的MySQL配置也改一下:
image

另外由于我本地的MySQL是8.X版本,所以驱动要升级。
image

我使用的JDK11,com.alibaba.fescar.tm.dubbo.impl.OrderServiceImpl有个BUG,运行时会报错,需要修改一下:
image

最后执行以下SQL语句初始化表:

DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `commodity_code` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT 0,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `money` int(11) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

好了,准备工作做完。
实例简述:
该实例模拟一个下单功能,由businessService发起,首先扣减库存,然后创建订单。
image

启动server项目的com.alibaba.fescar.server.Server的main方法;
启动example项目的AccountServiceImpl、OrderServiceImpl、StorageServiceImpl三个类的main方法,也就是dubbo服务提供者;
以上4个服务启动完成后,查看DB中的记录,会初始化account_tbl和storage_tbl两张表,插入一条记录(左侧的表)
image

执行com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImplmain方法。会发现执行报错,DB表数据没有变更。
是因为在com.alibaba.fescar.tm.dubbo.impl.BusinessServiceImpl#purchase方法中存在模拟的异常,我们将其注释掉,再次执行:
image

注释掉以后执行,可以发现没有报错,DB中的数据已经正确修改(参见上图的右侧三张表的数据)。

至此demo运行成功。

事务回滚原理简介

根据 阿里巴巴开源分布式事务解决方案 Fescar 介绍的原理,简单看看其rollback的原理。后续专门分析一下fescar的源码。

阿里巴巴开源分布式事务解决方案 Fescar 讲到它是优化了两阶段提交,减少锁的时间,利用本地事务真正提交事务,并且记录可用于回滚的日志,然后出错时根据日志回滚。

TransactionalTemplate是核心入口类;

/*
 *  Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.alibaba.fescar.tm.api;

import com.alibaba.fescar.core.exception.TransactionException;

/**
 * Template of executing business logic with a global transaction.
 */
public class TransactionalTemplate {

    /**
     * Execute object.
     *
     * @param business the business
     * @return the object
     * @throws TransactionalExecutor.ExecutionException the execution exception
     */
    public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {

        // 1. get or create a transaction
        //获取全局事务管理器
        GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();

        // 2. begin transaction  事务begin
        try {
            tx.begin(business.timeout(), business.name());

        } catch (TransactionException txe) {
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.BeginFailure);

        }

        Object rs = null;
        try {

            // Do Your Business  执行我们具体的业务逻辑
            rs = business.execute();

        } catch (Throwable ex) {

            // 3. any business exception, rollback.  出错时回滚
            try {
                tx.rollback();

                // 3.1 Successfully rolled back
                throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);

            } catch (TransactionException txe) {
                // 3.2 Failed to rollback
                throw new TransactionalExecutor.ExecutionException(tx, txe,
                    TransactionalExecutor.Code.RollbackFailure, ex);

            }

        }

        // 4. everything is fine, commit.  事务提交
        try {
            tx.commit();

        } catch (TransactionException txe) {
            // 4.1 Failed to commit
            throw new TransactionalExecutor.ExecutionException(tx, txe,
                TransactionalExecutor.Code.CommitFailure);

        }
        return rs;
    }

}

事务提交有两种:

public enum GlobalTransactionRole {

    /**
     * The Launcher.
     * 开启全局事务的发起者
     */
    // The one begins the current global transaction.
    Launcher,

    /**
     * The Participant.
     * 分支事务,也就是分布在各个系统中的本地事务
     */
    // The one just joins into a existing global transaction.
    Participant
}

通过代码可以看到,分支事务什么都不做,也就是直接提交本地事务。Launcher事务会进行全局事务的提交。
image

记录回滚日志的关键代码com.alibaba.fescar.rm.datasource.undo.UndoLogManager#flushUndoLogs中的undoLogContent

public static void flushUndoLogs(ConnectionProxy cp) throws SQLException {
        assertDbSupport(cp.getDbType());

        ConnectionContext connectionContext = cp.getContext();
        String xid = connectionContext.getXid();
        long branchID = connectionContext.getBranchId();

        BranchUndoLog branchUndoLog = new BranchUndoLog();
        branchUndoLog.setXid(xid);
        branchUndoLog.setBranchId(branchID);
        branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems());

        String undoLogContent = UndoLogParserFactory.getInstance().encode(branchUndoLog);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Flushing UNDO LOG: " + undoLogContent);
        }

        PreparedStatement pst = null;
        try {
            pst = cp.getTargetConnection().prepareStatement(INSERT_UNDO_LOG_SQL);
            pst.setLong(1, branchID);
            pst.setString(2, xid);
            pst.setBlob(3, BlobUtils.string2blob(undoLogContent));
            pst.executeUpdate();
        } catch (Exception e) {
            if (e instanceof SQLException) {
                throw (SQLException) e;
            } else {
                throw new SQLException(e);
            }
        } finally {
            if (pst != null) {
                pst.close();
            }
        }

    }

这里根据上面的实例,查看其中一条日志:

{
    "branchId": 1890459,
    "sqlUndoLogs": [{
        "afterImage": {
            "rows": [{
                "fields": [{
                    "keyType": "PrimaryKey",
                    "name": "ID",
                    "type": 4,
                    "value": 8
                }, {
                    "keyType": "NULL",
                    "name": "MONEY",
                    "type": 4,
                    "value": 199
                }]
            }],
            "tableName": "account_tbl"
        },
        "beforeImage": {
            "rows": [{
                "fields": [{
                    "keyType": "PrimaryKey",
                    "name": "ID",
                    "type": 4,
                    "value": 8
                }, {
                    "keyType": "NULL",
                    "name": "MONEY",
                    "type": 4,
                    "value": 599
                }]
            }],
            "tableName": "account_tbl"
        },
        "sqlType": "UPDATE",
        "tableName": "account_tbl"
    }],
    "xid": "10.240.130.105:8091:1890457"
}

可以看到,日志中记录了修改之前和之后的数据变化情况,也就是数据镜像,回滚时就是根据这个进行回滚的。

由于现在fescar才刚刚开源,远没有达到商用,需要到1.0版本才可以线上使用。本文只是简单了解入门一下,后续在升级几个版本之后再详细的分析其源码。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章