• 阿里开源分布式事务Fescar demo示例


    文章目录
    简介
    运行官方demo
    事务回滚原理简介
    简介
    阿里巴巴近日开源了分布式事务中间件 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官方wiki。
    fescar架构简介参考: 阿里巴巴开源分布式事务解决方案 Fescar

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

    我这里使用的是0.1.1版本:

    运行程序首先需要配置数据库并且初始化SQL。
    1、修改以下三个配置文件的MySQL地址

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

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

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

    最后执行以下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;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    好了,准备工作做完。
    实例简述:
    该实例模拟一个下单功能,由businessService发起,首先扣减库存,然后创建订单。

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

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

    注释掉以后执行,可以发现没有报错,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;
        }

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    事务提交有两种:

    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    通过代码可以看到,分支事务什么都不做,也就是直接提交本地事务。Launcher事务会进行全局事务的提交。

    记录回滚日志的关键代码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();
                }
            }

        }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    这里根据上面的实例,查看其中一条日志:

    {
        "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"
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    可以看到,日志中记录了修改之前和之后的数据变化情况,也就是数据镜像,回滚时就是根据这个进行回滚的。

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

  • 相关阅读:
    服务器架构前面加了防火墙,Nginx如何获取客户端真实ip???
    Prometheus学习笔记(5)Grafana可视化展示
    Prometheus学习笔记(4)什么是pushgateway???
    Prometheus学习笔记(3)什么是node_exporter???
    Prometheus学习笔记(2)Prometheus部署
    Prometheus学习笔记(1)Prometheus架构简介
    Centos 7 中的ulimit -n 65535 对进程的文件句柄限制不生效??
    Jenkins实用发布与回滚PHP项目生产实践
    Ansible入门笔记(3)之Playbook
    Ansible入门笔记(2)之常用模块
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13317332.html
Copyright © 2020-2023  润新知