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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 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

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
23天前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
|
1天前
|
存储 NoSQL Redis
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
Redis持久化、RDB和AOF方案、Redis主从集群、哨兵、分片集群、散列插槽、自动手动故障转移
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
|
1天前
|
SQL NoSQL 数据库
SpringCloud基础6——分布式事务,Seata
分布式事务、ACID原则、CAP定理、Seata、Seata的四种分布式方案:XA、AT、TCC、SAGA模式
SpringCloud基础6——分布式事务,Seata
|
1天前
|
消息中间件 存储 Java
SpringCloud基础9——服务异步通信-高级篇
消息可靠性、死信交换机、惰性队列、MQ集群
SpringCloud基础9——服务异步通信-高级篇
|
26天前
|
Java 微服务 Spring
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
文章介绍了如何利用Spring Cloud Alibaba快速构建大型电商系统的分布式微服务,包括服务限流降级等主要功能的实现,并通过注解和配置简化了Spring Cloud应用的接入和搭建过程。
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
|
20天前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
25天前
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
44 0
|
27天前
|
Java 应用服务中间件 数据库
SpringCloud:服务保护和分布式事务详解
SpringCloud:服务保护和分布式事务详解
85 0
|
28天前
|
缓存 Java Maven
SpringCloud基于Eureka的服务治理架构搭建与测试:从服务提供者到消费者的完整流程
Spring Cloud微服务框架中的Eureka是一个用于服务发现和注册的基础组件,它基于RESTful风格,为微服务架构提供了关键的服务注册与发现功能。以下是对Eureka的详细解析和搭建举例。
|
4月前
|
存储 关系型数据库 MySQL
基于Seata实现分布式事务
通过以上步骤,你可以使用 Seata 实现分布式事务,确保在微服务架构中的事务一致性。Seata 支持多种语言和框架,能够满足不同业务场景的需求。欢迎关注威哥爱编程,一起学习成长。