分布式事务对比

  • Seatamysql

  • EasyTransactiongit

  • hmilygithub

    • Hmily是由碧桂园工程师开发,高性能异步分布式事务TCC框架。web

      Github地址:https://github.com/yu199195/hmilyspring

  • bytetccsql

    • Bytetcc是由北京新奥集团工程师开发,是一个兼容JTA规范的基于TCC机制的分布式事务管理器。目前开发到了第五版,稳定版本为第五版,本次调研为第四版(0.5.x)。数据库

      GitHub地址:https://github.com/liuyangming/ByteTCC网络

Seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源以前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。通过多年沉淀与积累,商业化产品前后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,将来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。架构

  • 微服务框架支持app

    目前已支持 Dubbo、Spring Cloud、Sofa-RPC、Motan 和 grpc 等RPC框架,其余框架持续集成中

  • AT 模式

    提供无侵入自动补偿的事务模式,目前已支持 MySQL、 Oracle 的AT模式、PostgreSQL、H2 开发中

  • TCC 模式

    支持 TCC 模式并可与 AT 混用,灵活度更高

  • SAGA 模式

    为长事务提供有效的解决方案

  • XA 模式(开发中)

    支持已实现 XA 接口的数据库的 XA 模式

  • 高可用

    支持基于数据库存储的集群模式,水平扩展能力强

Seata术语

TC - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

EasyTransaction概述

EasyTransaction(后简称ET)的目标也是构建出一个全面分布式事务解决方案,到目前为止其包含TCC, 自动补偿(Seata AT),手动补偿,可靠事务消息、Saga事务等等多种形态。

TC差别

Seata:

  • 有独立部署的集中式TC
    • RM、TM与TC的交互经过RPC进行
  • TC中能够看到每一个具体的事务分支,统一协调全部事务分支的提交及回滚

EasyTransaction:

  • TC是业务服务进程中的一个模块,并无独立出来,且存在子事务的服务实例都会调用起TC模块
    • 如ServiceA,B,C都有TC,但ServiceD,E没有
    • 这里的有无指代的是这一次事务里TC的功能有没有被触发
    • TC与TM,RM的交互在进程内进行
  • 每一个TC只能协调其直接子事务
    • 如ServiceA的TC只知道ServiceB及ServiceE的子事务
    • 但ServiceB知道还有ServiceC的子事务,所以能够递归协调
    • 每一个服务实例发起的全局事务,服务实例自身会先尝试一次自协调(大多数都能成功),若自协调失败,则由一个服务实例兜底处理事务协调

Seata有集中式的TC,这样其能够 *更容易* 实现对分布式事务的监管控,如关于APM、Metrics、统一限流 等等功能。

但在EasyTransaction中的TC形式能够节约TC网络交互时间,在上述Seata的业务图中,ET的形式在两阶段提交中的第一阶段能节约6次 网络来回时间 及 正反序列化时间。

自动补偿实现差别

Seata:

  • 全局锁经过TC保存并实现

EasyTransaction:

  • 全局锁经过本地业务数据库保存

Seata经过TC保存全局记录锁引入了更多的复杂度,但其能自由控制锁的实现,能针对场景实现出效率更高的锁。

EasyTransction改造Seata的自动补偿功能,将原有的远程TC依赖改形成了EasyTransaction的分布式TC,并将全局锁实现改造到业务DB中。自动补偿的总体实现复杂度下降了,但性能可能有所降低(未经测试)。

不过Seata相关的实现也在进行中

seata AT 代码使用

pom 引入

<properties>
        <seata-mysql-support.version>0.0.1-SNAPSHOT</seata-mysql-support.version>
    </properties>
    
 	<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.timeloit.cloud</groupId>
                <artifactId>spring-cloud-timeloit</artifactId>
                <version>0.0.1-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
   
        <!-- nacos -->
        <dependency>
            <groupId>com.timeloit.cloud</groupId>
            <artifactId>spring-cloud-timeloit-nacos-discovery</artifactId>
        </dependency>

        <!-- seata-->
        <dependency>
            <groupId>com.timeloit.cloud</groupId>
            <artifactId>spring-cloud-timeloit-seata</artifactId>
        </dependency>

        <!-- mysql -->
        <dependency>
            <groupId>com.timeloit.cloud</groupId>
            <artifactId>seata-mysql-support</artifactId>
            <version>${seata-mysql-support.version}</version>
        </dependency>

引入配置

# Nacos 注册中心地址
spring.cloud.nacos.discovery.server-addr = 192.168.66.40:8848

# seata 服务分组,要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应
spring.cloud.alibaba.seata.tx-service-group=loit_server_tx_group

logging.level.io.seata = debug


# 数据源配置
spring.datasource.druid.url=jdbc:mysql://39.98.202.173:3306/seata_order?allowMultiQueries=true
spring.datasource.druid.driverClassName=com.mysql.jdbc.Driver
spring.datasource.druid.username=root
spring.datasource.druid.password=abcd1234A!

须要事务的地方添加@GlobalTransactional

如:

@GlobalTransactional
    @Transactional(rollbackFor = Exception.class)
    public void placeOrder(String userId, String commodityCode, Integer count) {
        BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
        Order order = new Order()
                .setUserId(userId)
                .setCommodityCode(commodityCode)
                .setCount(count)
                .setMoney(orderMoney);
        orderDAO.insert(order);
        storageFeignClient.deduct(commodityCode, count);

    }