分布式事务解决方案-seata试用

解决方案

在这里插入图片描述

框架

从使用成本的角度来看,直接使用开源的分布式事务框架是比较容易上手的,所以寻找了比较流行的分布式事务框架seata(阿里开源组件)、Hmily(TCC模式)、 tcc-transaction(TCC模式)

seatahmilytcc-transaction
文档不全
支持模式AT/tcc/saga/XAtcctcc
star18.6k3.4k4.9k

对比了几款开源框架,阿里的开源框架seata使用人数最多,支持模式最全,文档也相对较多,所以选用seata进行了试用,下面记录下seata的使用步骤。

seata
原理

Seata的分布式事务解决方案是业务层面的解决方案,只依赖于单台数据库的事务能力。Seata框架中一个分布式事务包含3中角色:

  • Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

    (就是我们的Seata-server,用来保存全局事务,分支事务,全局锁等记录,然后会通知各个RM进行回滚或者提交.)

  • Transaction Manager ™: 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

    ( 也是我们的一个微服务,但是该微服务是一个带头大哥,充当全局事务的发起者(决定了全局事务的开启,回滚,提交等)凡是我们的微服务中标注了@GlobalTransactional ,那么该微服务就会被看成一个TM,同时也是一个RM。)

  • Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

    (可以理解为我们的一个个微服务,也叫事务的参与者)

其中,TM是一个分布式事务的发起者和终结者,TC负责维护分布式事务的运行状态,而RM则负责本地事务的运行。如下图所示:

在这里插入图片描述

下面是一个分布式事务在Seata中的执行流程:

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  2. XID 在微服务调用链路的上下文中传播。
  3. RM 向 TC 注册分支事务,接着执行这个分支事务并提交(重点:RM在第一阶段就已经执行了本地事务的提交/回滚),最后将执行结果汇报给TC。
  4. TM 根据 TC 中所有的分支事务的执行情况,发起全局提交或回滚决议。
  5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
使用
1.提前搭建nacos作为配置中心和注册中心
2.seata-server搭建
## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  mode = "db"
  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://ip:3306/seata"
    user = username
    password = password
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}
  • 对registry.conf进行修改,这里主要进行注册中心和配置中心的配置,此处均选择nacos作为注册中心和配置中心,注册中心和配置中心需要使用其它的组件请自行研究Seata 是什么
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "http://ip:8848"
    group = "SEATA_GROUP"
    #nacos中的命名空间ID
    namespace = "a86602c7-0172-4348-9821-e8f28fe4b80e"
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"
  nacos {
    serverAddr = "http://ip:8848"
    #nacos中的命名空间ID
    namespace = "a86602c7-0172-4348-9821-e8f28fe4b80e"
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
}

  • 修改完配置后,可以启动seata-server,启动脚本在\seata-server-1.4.0\seata\bin 文件夹下面,
  • 启动脚本:nohup sh seata-server.sh -p 8091 -h nacosip -m db &
  • 可以在nacos上看到,seata-server已经成功注册上了

在这里插入图片描述

3.导入配置中心配置

下载配置文件config.txtseata/config.txt at develop · seata/seata (github.com)

下载nacos导入脚本seata/script/config-center/nacos at develop · seata/seata (github.com)

修改配置文件,只需修改service.vgroupMapping.tx-service-group和数据库配置即可

#最需要关注的事务分组——tx-service-group 这值会在我们客户端对应,需要注意
service.vgroupMapping.tx-service-group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
#数据库地址和之前file.conf中的数据库配置一致
store.db.url=jdbc:mysql://ip:3306/seata?useUnicode=true
store.db.user=username
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000

在windoes下执行脚本,sh nacos-config.sh -h nacosip -p 8848,把配置成功导入到nacos中

在这里插入图片描述

4.搭建微服务

一、创建事务日志表

测试工程由3个微服务构成,数据库也是三个,每个数据库需要提前创建一个事务日志表,sql可在此处下载seata/script/client/at/db at develop · seata/seata (github.com)

二、添加pom依赖

	   <dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
			<exclusions>
				<exclusion>
					<artifactId>seata-all</artifactId>
					<groupId>io.seata</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>io.seata</groupId>
			<artifactId>seata-all</artifactId>
			<version>1.4.0</version>
		</dependency>

三、修改启动类注解

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

四、添加代理数据源配置

@Configuration
@MapperScan(basePackages = {"com.demo.seata.mapper"})
public class MyBatisConfig {
    /**
     * 从配置文件获取属性构造datasource,注意前缀,这里用的是druid,根据自己情况配置,
     * 原生datasource前缀取"spring.datasource"
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public DataSource hikariDataSource() {
        return new HikariDataSource();
    }
    /**
     * 构造datasource代理对象,替换原来的datasource
     *
     * @param hikariDataSource
     * @return
     */
    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSourceProxy(DataSource hikariDataSource) {
        return new DataSourceProxy(hikariDataSource);
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:/mybatis/mapper/**/*.xml"));
        sqlSessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:/mybatis/mybatis-config.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.demo.seata.domin");
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        return sqlSessionFactoryBean;
    }

}

五、添加及修改配置

#seata配置(配置事务组 需要和seata‐server的配置一样)
spring.cloud.alibaba.seata.tx-service-group=tx-service-group

下载client的配置文件file.conf和register.conf,下载地址seata/script/client/conf at develop · seata/seata (github.com)

client端的file.conf、register.conf配置和seata-server的file.conf、register.conf一致,请按照自身情况进行修改

在这里插入图片描述
在这里插入图片描述

六、微服务发起者加上全局事务注解@GlobalTransactional

    @GlobalTransactional(name = "prex-create-order",timeoutMills = 30000,rollbackFor = Exception.class)
    //@Transactional
    @Override
    public void createOrder(Order order) {
        log.info("当前 XID: {}", RootContext.getXID());
        log.info("下单开始,用户:{},商品:{},数量:{},金额:{}", order.getUserId(), order.getProductId(), order.getCount(), order.getPayMoney());
        //创建订单
        order.setStatus(0);
        orderMapper.saveOrder(order);
        log.info("保存订单{}", order);

        //远程调用库存服务扣减库存
        log.info("扣减库存开始");

        remoteStorageService.reduceCount(order.getProductId(), order.getCount());

        log.info("扣减库存结束");

        //远程调用账户服务扣减余额
        log.info("扣减余额开始");
        remoteAccountService.reduceBalance(order.getUserId(), order.getPayMoney());
        log.info("扣减余额结束");

        //System.out.println(1/0);
        //修改订单状态为已完成
        log.info("修改订单状态开始");
        orderMapper.updateOrderStatusById(order.getId(),1);
        log.info("修改订单状态结束");

        log.info("下单结束");
    }

六、测试

在这里插入图片描述

经测试,在调用链的任意一处出现异常,都可以成功回滚 出现异常的服务及所有上游服务的sql

至此,已经完成了seata的一个简单试用,可以看到seata的搭建是比较复杂的,但是使用起来比较简单,只需要一个注解就可以达到解决分布式事务的问题,此处试用的是seata最简单的AT模式,seata还支持tcc/saga/xa模式,此处不再介绍。

当然实际应用中业务可能更复杂,需要进一步测试进行验证,本文仅做分布式事务方案的一个初步试用,具体使用什么解决方案和框架还要做深入探讨。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值