Seata实现分布式事务(2)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Seata实现分布式事务(2)

2 基于Seata实现分布式事务

2.1 Seata简介

官网地址:http://seata.io/zh-cn/

20210216224933918.png

2021021622494418.png

  • Seata用于解决分布式事务
  • Seata非常适合解决微服务分布式事务【dubbo、SpringCloud….】
  • Seata性能高
  • Seata使用简单

2.2 Seata事务模式-AT模式

20210216224955900.png

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

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

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

一个典型的分布式事务过程:

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  2. XID 在微服务调用链路的上下文中传播。
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
  1. TM 向 TC 发起针对 XID 的全局提交或回滚决议。
  2. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

AT模式使用前提:

  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。

AT模式机制:

基于两阶段提交协议的演变。

一阶段:

业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

20210216225008529.png

二阶段:

如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),Phase2 可以非常快速地完成。

202102162250228.png

如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。

20210216225321428.png

20210216225334273.png

注意此处seata版本是0.7.0+ 增加字段 context

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

AT模式部分代码如下:不需要关注执行状态,对业务代码侵入较小。

/**
  * 此代码为示例代码, 不需要演示, 主要看AT和TCC代码的区别使用
  */
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
    LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
    storageService.deduct(commodityCode, orderCount);
    orderService.create(userId, commodityCode, orderCount);
    throw new RuntimeException("AT 模式发生异常,回滚事务");
}

2.3 Seata Server端环境准备

(1)从官网上下载seata server端的程序包,解压到一个地方

下载地址:https://github.com/seata/seata/releases

2021021622534895.png

(2)修改配置

我们是基于file的方式启动注册和承载配置的

20210216225359336.png

打开conf/file.conf文件

修改service 节点目录内容如下:

service {
  #vgroup->rgroup
  vgroup_mapping.my_test_tx_group = "default"
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
  #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
}

说明:需要修改default.grouplist = “127.0.0.1:8091”,将该值设置为seata server向外提供服务ip及端口(或域名+端口)

(4)启动server

到bin目录下执行脚本启动seata server端,注:windows下执行seata-server.bat启动;linux下执行seata-server.sh启动

2.4 项目集成seata-仔细点

2.4.1 创建日志表undo_log

分别在leadnews_article、leadnews_user、leadnews_wemedia三个库中都创建undo_log表。已经做好。

2.4.2 导入依赖包

因为有多个工程都需要引入seata,所以新建一个工程oldlu-leadnews-seata专门来处理分布式事务

<dependencies>
    <dependency>
        <groupId>com.oldlu</groupId>
        <artifactId>oldlu-leadnews-common</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-all</artifactId>
        <version>0.9.0</version>
        <exclusions>
            <exclusion>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>
</dependencies>

2.4.3 创建代理数据源

(1)因为多个工程都需要依赖与seata,所以在oldlu-leadnews-seata模块下创建seata的配置类

package com.oldlu.seata.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@EnableConfigurationProperties({MybatisPlusProperties.class})
public class DataSourcesProxyConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
    //创建代理数据源
    @Primary//@Primary标识必须配置在代码数据源上,否则本地事务失效
    @Bean
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
    private MybatisPlusProperties properties;
    public DataSourcesProxyConfig(MybatisPlusProperties properties) {
        this.properties = properties;
    }
    //替换SqlSessionFactory的DataSource
    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactory(DataSourceProxy dataSourceProxy, PaginationInterceptor paginationInterceptor) throws Exception {
        // 这里必须用 MybatisSqlSessionFactoryBean 代替了 SqlSessionFactoryBean,否则 MyBatisPlus 不会生效
        MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        mybatisSqlSessionFactoryBean.setDataSource(dataSourceProxy);
        mybatisSqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        mybatisSqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath*:/mapper/*.xml"));
        MybatisConfiguration configuration = this.properties.getConfiguration();
        if(configuration == null){
            configuration = new MybatisConfiguration();
        }
        mybatisSqlSessionFactoryBean.setConfiguration(configuration);
        //设置分页
        Interceptor[] plugins = {paginationInterceptor};
        mybatisSqlSessionFactoryBean.setPlugins(plugins);
        return mybatisSqlSessionFactoryBean;
    }
}

(2)分别在oldlu-leadnews-article、oldlu-leadnews-user、oldlu-leadnews-wemedia引入oldlu-leadnews-seata工程

    <dependency>
            <groupId>com.oldlu</groupId>
            <artifactId>oldlu-leadnews-seata</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

并且添加一下配置类:

@Configuration
@ComponentScan("com.oldlu.seata.config")
public class SeataConfig {
}

2.4.4 配置seata-server链接和注册中心信息

修改注册中心配置,在每个项目中必须按照下方要求来

将配置文件file.conf和配置文件register.conf放到每个需要参与分布式事务项目的resources中。

  • file.conf中的service.default.grouplist修改成seata-server的IP地址file.conf中的
  • service.vgroup_mapping.xxx改成vgroup_mapping.#{spring.application.name}_tx_group = “default”

特别注意:#{spring.application.name}是一个变量,指的是该项目的名称

如自媒体微服务名称的项目名称如下:

20210217111907292.png

那么其配置就是vgroup_mapping.leadnews-wemedia_tx_group = "default"

其他项目也是这么依次配置

2.4.5 指定事务分组

分别在oldlu-leadnews-article、oldlu-leadnews-user、oldlu-leadnews-wemedia微服务的application.yml文件中添加如下配置:

spring:
  cloud:
    alibaba:
      seata:
        tx-service-group: ${spring.application.name}_tx_group

2.4.6 在分布式事务控制方法上添加注解@GlobalTransactional

在ApUserRealnameServiceImpl类的updateStatusById方法上加上@GlobalTransactional注解


2.4.7 启动seata-server

运行:

/seata/bin/seata-server.bat


20210216225423715.png

2.4.8 测试

(1)功能测试,看功能能否正常执行。

(2)异常测试,我们在方法中添加int x=1/0 ,看认证信息和自媒体用户是否能够回滚。

2021021622543499.png

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
18天前
|
消息中间件 SQL 中间件
大厂都在用的分布式事务方案,Seata+RocketMQ带你打破10万QPS瓶颈
分布式事务涉及跨多个数据库或服务的操作,确保数据一致性。本地事务通过数据库直接支持ACID特性,而分布式事务则需解决跨服务协调难、高并发压力及性能与一致性权衡等问题。常见的解决方案包括两阶段提交(2PC)、Seata提供的AT和TCC模式、以及基于消息队列的最终一致性方案。这些方法各有优劣,适用于不同业务场景,选择合适的方案需综合考虑业务需求、系统规模和技术团队能力。
133 7
|
1月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
54 6
|
1月前
|
数据库
如何在Seata框架中配置分布式事务的隔离级别?
总的来说,配置分布式事务的隔离级别是实现分布式事务管理的重要环节之一,需要认真对待和仔细调整,以满足业务的需求和性能要求。你还可以进一步深入研究和实践 Seata 框架的配置和使用,以更好地应对各种分布式事务场景的挑战。
32 6
|
29天前
|
消息中间件 运维 数据库
Seata框架和其他分布式事务框架有什么区别
Seata框架和其他分布式事务框架有什么区别
28 1
|
3月前
|
SQL NoSQL 数据库
SpringCloud基础6——分布式事务,Seata
分布式事务、ACID原则、CAP定理、Seata、Seata的四种分布式方案:XA、AT、TCC、SAGA模式
SpringCloud基础6——分布式事务,Seata
|
4月前
|
关系型数据库 MySQL 数据库
SpringCloud2023中使用Seata解决分布式事务
对于分布式系统而言,需要保证分布式系统中的数据一致性,保证数据在子系统中始终保持一致,避免业务出现问题。分布式系统中对数据的操作要么一起成功,要么一起失败,必须是一个整体性的事务。Seata简化了这个使用过程。
100 2
|
4月前
|
Java 关系型数据库 MySQL
(二十七)舞动手指速写一个Seata-XA框架解决棘手的分布式事务问题
相信大家对于事务问题都不陌生,在之前《MySQL事务篇》中曾详解过MySQL的事务机制,在传统的单库环境下开发,咱们可依赖于MySQL所提供的事务机制,来确保单个事务内的一组操作,要么全部执行成功,要么全部执行失败。
|
4月前
|
Java Nacos Docker
"揭秘!Docker部署Seata遇上Nacos,注册成功却报错?这些坑你不得不防!一网打尽解决秘籍,让你的分布式事务稳如老狗!"
【8月更文挑战第15天】在微服务架构中,Nacos搭配Seata确保数据一致性时,Docker部署Seata后可能出现客户端连接错误,如“can not connect to services-server”。此问题多由网络配置不当、配置文件错误或版本不兼容引起。解决策略包括:调整Docker网络设置确保可达性;检查并修正`file.conf`和`registry.conf`中的Nacos地址和端口;验证Seata与Nacos版本兼容性;修改配置后重启服务;参考官方文档和最佳实践进行配置。通过这些步骤,能有效排除故障,保障服务稳定运行。
359 0
|
6月前
|
Java 数据库 开发者
深入解析 Spring Cloud Seata:分布式事务的全面指南
深入解析 Spring Cloud Seata:分布式事务的全面指南
334 1