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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 分布式事务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版本才可以线上使用。本文只是简单了解入门一下,后续在升级几个版本之后再详细的分析其源码。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
9天前
|
程序员
后端|一个分布式锁「失效」的案例分析
小猿最近很苦恼:明明加了分布式锁,为什么并发还是会出问题呢?
19 2
|
14天前
|
消息中间件 架构师 Java
阿里面试:秒杀的分布式事务, 是如何设计的?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试阿里、滴滴、极兔等一线互联网企业时,遇到了许多关于分布式事务的重要面试题。为了帮助大家更好地应对这些面试题,尼恩进行了系统化的梳理,详细介绍了Seata和RocketMQ事务消息的结合,以及如何实现强弱结合型事务。文章还提供了分布式事务的标准面试答案,并推荐了《尼恩Java面试宝典PDF》等资源,帮助大家在面试中脱颖而出。
|
27天前
|
分布式计算 Hadoop 网络安全
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
27 1
|
27天前
|
存储 机器学习/深度学习 缓存
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
37 1
|
20天前
|
存储 缓存 数据处理
深度解析:Hologres分布式存储引擎设计原理及其优化策略
【10月更文挑战第9天】在大数据时代,数据的规模和复杂性不断增加,这对数据库系统提出了更高的要求。传统的单机数据库难以应对海量数据处理的需求,而分布式数据库通过水平扩展提供了更好的解决方案。阿里云推出的Hologres是一个实时交互式分析服务,它结合了OLAP(在线分析处理)与OLTP(在线事务处理)的优势,能够在大规模数据集上提供低延迟的数据查询能力。本文将深入探讨Hologres分布式存储引擎的设计原理,并介绍一些关键的优化策略。
71 0
|
2月前
|
网络协议 安全 Java
分布式(基础)-RMI的原理
分布式(基础)-RMI的原理
|
3月前
|
运维 安全
基于simulink的分布式发电系统自动重合闸的建模与仿真分析
本课题研究配电系统中分布式电源接入后的自动重合闸问题,着重分析非同期重合闸带来的冲击电流及其影响。通过Simulink搭建模型,仿真不同位置及容量的分布式电源对冲击电流的影响,并对比突发性和永久性故障情况。利用MATLAB2022a进行参数设置与仿真运行,结果显示非同期重合闸对系统安全构成挑战,需通过优化参数提升系统性能。
|
4月前
|
存储 关系型数据库 MySQL
深度评测:PolarDB-X 开源分布式数据库的优势与实践
本文对阿里云开源分布式数据库 PolarDB-X 进行了详细评测。PolarDB-X 以其高性能、强可用性和出色的扩展能力在云原生数据库市场中脱颖而出。文章首先介绍了 PolarDB-X 的核心产品优势,包括金融级高可靠性、海量数据处理能力和高效的混合负载处理能力。随后,分析了其分布式架构设计,包括计算节点、存储节点、元数据服务和日志节点的功能分工。评测还涵盖了在 Windows 平台通过 WSL 环境部署 PolarDB-X 的过程,强调了环境准备和工具安装的关键步骤。使用体验方面,PolarDB-X 在处理分布式事务和实时分析时表现稳定,但在网络问题和性能瓶颈上仍需优化。最后,提出了改进建
6955 2
|
4月前
|
NoSQL Redis 数据库
|
18天前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?