十、SpringCloud Alibaba Seata

CY 2019年03月24日 17次浏览

使用分布式难免会遇到分布式事务的问题,因为一个功能可能使用到多个微服务,需要跨多个数据源才能解决。

Seata解决分布式事务

以下的内容非常的表面,没有任何深入的部分,仅仅记录了如何简单的使用,更多深入的内容可以查阅官网,或者网上一些文章。

没有分布式事务遇到的问题

一切概念的前提是先看效果,不然概念怎么也看不懂。

Github Release中下载Seata,下载GA版本就好了。

一如既往的解压,然后运行,但是因为我电脑使用的是JDK11,所以Seata启动命令里面的很多JVM参数无法使用,在不修改JAVA_HOME的情况下去使用这些参数,就需要修改启动命令了。

Windows为例,seata-server.bat中的代码修改如下:

# ...省略70行代码

if "%JAVACMD%"=="" set JAVACMD="C:\Program Files\Java\jdk1.8.0_221\bin\java"

# ...省略余下所有代码

这样就可以明确指定JDK要使用1.8版本。

然后就可以启动Seata了,双击启动脚本即可。

现在要做的目的,是演示出分布式事务的效果,为了尽量的简单,就用三个微服务,连接三个数据库,每个数据库中有一张表,表中只有idnumber,例如这样:

idnumber
1100

三个数据库都是如此。

现在要实现的效果是,三个微服务(A,B,C),每个微服务使用一个数据库,A来调用B和C的微服务来完成自己的工作。

目标是访问A微服务,三个数据库中的number都要减一,变成99,98...

先搭建起来基础的功能框架,已经成千上百遍了,就略过了。

最终的三个模块如下:

image.png

伪代码:

public void decrease(Long id) {
    // 数据库1中的number - 1
    repository.findById(id).ifPresent(x -> {
        repository.save(x.setNumber(x.getNumber() - 1));
    });
    // 数据库2中的number - 1
    service02.decrease(id);
    // 数据库3中的number - 1
    service03.decrease(id);
}

先测试一下没有加入分布式事务的情况:在C微服务中随手加入一个异常,比如说by zero,然后去看看数据库,发现A和B微服务操作数据成功了,但是C并没有操作成功,这就产生了数据不一致的情况。

加入分布式事务的支持

这个案例完全模仿官方的Demo,只不过官方的Demo已经不是新版本的了,新版本改变了一些东西。

首先要启动Seata,然后在三个微服务中都要加入下面的代码:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-seata</artifactId>
</dependency>
spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: default

然后需要在三个数据库中分别都要创建一张叫做undo_log的表,SQL点击查看

还需要将Seata目录下面的conf中的file.confregistry.conf拷贝到resources目录下

在A微服务的Service方法上加入@GlobalTransactional注解。

C微服务还是by zero异常,这个时候再去访问A服务,发现数据库中的数据并没有发生改变,因为C微服务出异常了,所以全部数据库已经回滚了,这就已经实现了分布式事务。

原理是因为Seata代理了原来的数据源,所以在官方的案例中会有如下代码:

package io.seata.sample.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * 数据源配置
 *
 * @author HelloWoodes
 */
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }

    /**
     * 需要将 DataSourceProxy 设置为主数据源,否则事务无法回滚
     *
     * @param druidDataSource The DruidDataSource
     * @return The default datasource
     */
    @Primary
    @Bean("dataSource")
    public DataSource dataSource(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
}

代码的目的是手动的配置Seata作为数据源的代理。

这个在新版中是不能直接用的,因为新版中已经自动将Seata作为数据源的代理了,如果非要手动去写代理数据源,需要加入下面的配置关闭自动代理数据源。

seata:
  client:
    support:
      spring:
        datasource-autoproxy: false

一些术语

XID

全局的事务ID

TC - 事务协调者

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

TM - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务,就是标注了@GlobalTransactional注解的服务A。

RM - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚,就是每个涉及到的服务,比如说上面例子中的A、B、C。

image.png

默认使用的是AT模式。

关于一些配置

Seata的配置文件重要的有两个:

registry.conffile.conf

registry.conf

  • registry是用来注册Seata到注册中心的
  • config是用来指定读取配置文件的位置,支持filenacosapollozkconsuletcd3

file.conf:

  • service配置对应application.yaml中的tx-service-group
  • store配置Seata所使用的数据源,可以配置成MySQL,支持两种模式filedb