SpringCloud Alibaba学习(十二):Seata处理分布式事务(三万字提供 介绍、搭建、实战、原理一条龙服务)(下)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: SpringCloud Alibaba学习(十二):Seata处理分布式事务(三万字提供 介绍、搭建、实战、原理一条龙服务)

3、新建账户Account-Module

             

(1)新建模块

             

新建普通maven模块 seata-account-service2003


(2)修改pom文件


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud</artifactId>
        <groupId>com.shang.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>seata-account-service2003</artifactId>
    <dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <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>0.9.0</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
</project>


(3)编写yml文件


server:
  port: 2003
spring:
  application:
    name: seata-account-service
  cloud:
    alibaba:
      seata:
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3308/seata_account
    username: root
    password: root
feign:
  hystrix:
    enabled: false
logging:
  level:
    io:
      seata: info
mybatis:
  mapperLocations: classpath:mapper/*.xml


(4)粘贴conf文件(1.0版本后已经支持yml)

               

将seata目录下的 file.conf 和 registry.conf 粘到resource目录下


(5) domain包

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>{
    private Integer code;
    private String  message;
    private T       data;
    public CommonResult(Integer code, String message)
    {
        this(code,message,null);
    }
}


@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
    private Long id;
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 总额度
     */
    private BigDecimal total;
    /**
     * 已用额度
     */
    private BigDecimal used;
    /**
     * 剩余额度
     */
    private BigDecimal residue;
}


(6)dao包


@Mapper
public interface AccountDao {
    /**
     * 扣减账户余额
     */
    void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
}


(7)service包


public interface AccountService {
    /**
     * 扣减账户余额
     * @param userId 用户id
     * @param money 金额
     */
    void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}


(8)service.impl包


@Service
public class AccountServiceImpl implements AccountService {
    private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
    @Resource
    AccountDao accountDao;
    /**
     * 扣减账户余额
     */
    @Override
    public void decrease(Long userId, BigDecimal money) {
        LOGGER.info("------->account-service中扣减账户余额开始");
        //模拟超时异常,全局事务回滚
        //暂停几秒钟线程
        //try { TimeUnit.SECONDS.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); }
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service中扣减账户余额结束");
    }
}


(9)controller包


@RestController
public class AccountController {
    @Resource
    AccountService accountService;
    /**
     * 扣减账户余额
     */
    @RequestMapping("/account/decrease")
    public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
        accountService.decrease(userId,money);
        return new CommonResult(200,"扣减账户余额成功!");
    }
}


(10)config包


同2001


(11)主启动类


@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataAccountMainApp2003{
    public static void main(String[] args) {
        SpringApplication.run(SeataAccountMainApp2003.class, args);
    }
}


七、测试


     

按顺序启动Nacos、Seata、数据库、三个微服务。

 

1、先看数据库初始情况

     

SELECT *  FROM  `seata_order`.`t_order`


5f742119f57142b18c0f2b75189ec7ad.png

可以看到order订单表里没有数据,此时还没有用户下单。

       

SELECT * FROM `seata_storage`.`t_storage`


06905d9141a8468da02d52cec0bbc70f.png


id为1(id=1表示这条记录在数据库中id为1,product_id = 1表示产品id为1)的产品总数量为100,已经使用0个,还剩100个。

       

SELECT *  FROM  `seata_account`.`t_account`;

140589c85beb4339b1f96f241a93a96f.png


id为1的用户账户中一共有1000,用了0,还剩1000


2、正常下单

     

浏览器访问:


http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

       表示id为1的用户购买id为1的产品,买了10个花了100


d7954a7920504c9aaf3426ae6b996a20.png


3、查看数据库情况

       

t_account

fdfc794c42e44fbb9de58b4689336fea.png


1号用户一共有1000,花了200还剩800

     

t_order

6e79205642b54cc99712a09206c48612.png


1号用户购买1号产品,买了10个花了100,交易成功(status=1)

       

t_storage


cca44d5e2eac44e58a52e9c4096dd4ac.png

1号产品一共有100个,卖出20个,还剩80个

     

可以看出我们的数据库里各个数据都是符合逻辑的,说明我们前面的微服务搭建没有问题。

     

那么我们就要开始测试事务回滚了。


4、模拟错误

     

在AccountServiceImpl添加睡眠,模拟超时异常

b09d5800aca348c280dbb7e243287690.png


5、再次下单,并查看数据库

     

下单当然不成功 ,关键是数据库里的数据如何变化。


39266e86420544dc9590821b755afb00.png

首先要清楚,我们希望的是在出现错误后数据回滚,数据库里所有的数据都不要发生变化。

     

由于没有事务处理操作,这显然是不可能的,让我们来看看数据库里变成什么了。

       

t_account

8af749d4e90b41ebb70e8291e9a29280.png

 用户账户还是被扣钱了

       

t_order


5187f2c251824ef694af211bf6c89a7d.png

交易记录还是出现了,并且是一个不成功(status=0)的记录

       

t_storage

cca44d5e2eac44e58a52e9c4096dd4ac.png


货物并没有变化。

这就相当于钱扣了,货没来,这显然不符合逻辑。 并且而且由于feign的重试机制,账户余额还有可能被多次扣减。


那么如何添加事务操作?


6、添加注解@GlobalTransactional

     

在订单的入口(OrderServiceImpl 的 create方法)上添加@GlobalTransactional 注解即可完成分布式事务的处理


     

7、再次下单测试

     

这个时候可以看到,数据库中的所有数据都没有发生变化。


     

这样,我们就完成了分布式事务的处理。


八、Seata原理浅析


     

1、再看TC/TM/RM三大组件

0adef6a987ec4cf6ac9c73ceb43249bc.png


在我们这次的实战中,TC其实就相当于我们的Seata;TM相当于我们的account微服务(@GlobalTransactional加上的地方);RM就是我们的三个数据库,每一个数据库就是一个RM


2、分布式事务的执行流程

             

1. TM 开启分布式事务(TM 向 TC 注册全局事务记录);

         

2. 按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );

           

3. TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);

             

4. TC汇总事务信息,决定分布式事务是提交还是回滚;


5. TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。


3、Seata的四种模式

703d138b775a4313a1d42ee07023da36.png


4、AT模式如何做到对业务的无侵入

             

(1)一阶段加载


在一阶段,Seata 会拦截“业务 SQL”,

     

1  解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,

2  执行“业务 SQL”更新业务数据,在业务数据更新之后,

3  其保存成“after image”,最后生成行锁。


以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。  


e390cd5e8e49457cbace694eee1615f9.png


(2)二阶段提交


       假如二阶段顺利提交,因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

915a4277ffff4a039de2d4bef7529877.png



(3)二阶段回滚


假如二阶段回滚,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。

回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。  



e8f7ba7185a840dcad166f3586e80ae0.png

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
3月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
197 53
|
29天前
|
Java 关系型数据库 数据库
微服务SpringCloud分布式事务之Seata
SpringCloud+SpringCloudAlibaba的Seata实现分布式事务,步骤超详细,附带视频教程
49 1
|
7月前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
243 3
|
2月前
|
消息中间件 SQL 中间件
大厂都在用的分布式事务方案,Seata+RocketMQ带你打破10万QPS瓶颈
分布式事务涉及跨多个数据库或服务的操作,确保数据一致性。本地事务通过数据库直接支持ACID特性,而分布式事务则需解决跨服务协调难、高并发压力及性能与一致性权衡等问题。常见的解决方案包括两阶段提交(2PC)、Seata提供的AT和TCC模式、以及基于消息队列的最终一致性方案。这些方法各有优劣,适用于不同业务场景,选择合适的方案需综合考虑业务需求、系统规模和技术团队能力。
281 7
|
3月前
|
消息中间件 数据库
Seata框架的工作原理
你还可以进一步深入研究 Seata 框架的技术细节和具体实现,以更好地理解其工作原理和优势。同时,结合实际应用场景进行实践和优化,也是提高分布式事务处理能力的重要途径。
63 15
|
3月前
|
数据库
如何在Seata框架中配置分布式事务的隔离级别?
总的来说,配置分布式事务的隔离级别是实现分布式事务管理的重要环节之一,需要认真对待和仔细调整,以满足业务的需求和性能要求。你还可以进一步深入研究和实践 Seata 框架的配置和使用,以更好地应对各种分布式事务场景的挑战。
131 58
|
3月前
|
消息中间件 运维 数据库
Seata框架和其他分布式事务框架有什么区别
Seata框架和其他分布式事务框架有什么区别
47 1
|
4月前
|
SQL JavaScript 数据库连接
Seata的工作原理
【10月更文挑战第30天】
78 3
|
5月前
|
SQL NoSQL 数据库
SpringCloud基础6——分布式事务,Seata
分布式事务、ACID原则、CAP定理、Seata、Seata的四种分布式方案:XA、AT、TCC、SAGA模式
SpringCloud基础6——分布式事务,Seata