【Seata】完成分布式事务控制

简介: 【Seata】完成分布式事务控制

一、Seata 分布式事务


1.1 概述


2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback),其愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们 遇到的分布式事务方面的所有难题。后来更名为 Seata,意为:Simple Extensible Autonomous Transaction Architecture,是一套分布式事务解决方案。


1.2 Seata的执行流程如下


A服务的TM向TC申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID


A服务的RM向TC注册分支事务,并及其纳入XID对应全局事务的管辖


A服务执行分支事务,向数据库做操作


A服务开始远程调用B服务,此时XID会在微服务的调用链上传播


B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖


B服务执行分支事务,向数据库做操作


全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚


TC协调其管辖之下的所有分支事务, 决定是否回滚

20200819201314417.png


二、模拟下单扣减库存


Order服务


2.1 OrderController

@RestController 
@Slf4j
public class OrderController5 { 
    @Autowired
    private OrderServiceImpl5 orderService;
    //下单
    @RequestMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
      log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid); 
      return orderService.createOrder(pid);
    }
}

2.2 OrderServiceImpl

@Service 
@Slf4j
public class OrderServiceImpl5{
@Autowired
private OrderDao orderDao;
@Autowired
private ProductService productService;
@Autowired
private RocketMQTemplate rocketMQTemplate;
//@GlobalTransactional // seata全局事务控制
@Transactional // 本地事务控制
public Order createOrder(Integer pid) {
    //1 调用商品微服务,查询商品信息
    Product product = productService.findByPid(pid);
    log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));
    //2 下单(创建订单)
    Order order = new Order(); order.setUid(1); order.setUsername(" 测 试 用 户 ");       order.setPid(pid); 
    order.setPname(product.getPname());
    order.setPprice(product.getPprice()); 
    order.setNumber(1); 
    orderDao.save(order);
    log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));
    //3 扣库存
    productService.reduceInventory(pid, order.getNumber());
    //4 向mq中投递一个下单成功的消息
    rocketMQTemplate.convertAndSend("order-topic", order);
    return order;
  }
}

2.3 ProductService

@FeignClient(value = "service-product")
public interface ProductService {
    //减库存
    @RequestMapping("/product/reduceInventory")
    void reduceInventory(@RequestParam("pid") Integer pid, @RequestParam("num") int num);
}

Product微服务


2.4 ProductController


//减少库存
@RequestMapping("/product/reduceInventory")
  public void reduceInventory(Integer pid, int num) {
    productService.reduceInventory(pid, num);
}

2.5 ProductService


@Override
public void reduceInventory(Integer pid, int num) { 
    Product product = productDao.findById(pid).get(); 
    product.setStock(product.getStock() - num);//减库存
    productDao.save(product);
}

2.6 模拟下单异常


在ProductServiceImpl的代码中模拟一个异常, 然后调用下单接口

@Override
public void reduceInventory(Integer pid, Integer number) { 
    Product product = productDao.findById(pid).get();
    if (product.getStock() < number) {
        throw new RuntimeException("库存不足");
    }
    int i = 1 / 0; // 异常所在
    //扣减库存操作
    product.setStock(product.getStock() - number); 
    productDao.save(product);
}

http://localhost:8091/order/prod/1

在2.2中的本地事务@Transactional,那么数据库会得到神魔结果呢???

答案不言而喻:订单创建成功,然后库存并没有得到响应的减少。


三、Seata引入


3.1 下载


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


3.2 修改配置文件


由于我这里使用的nacos注册中心作为管理。

将下载得到的压缩包进行解压,进入conf目录,调整下面的配置文件:

1.registry.conf


registry {
    type = "nacos" nacos {
    serverAddr = "localhost" namespace = "public" cluster = "default"
    }
}
config {
    type = "nacos" nacos {
    serverAddr = "localhost" namespace = "public" cluster = "default"
    }
}

2.nacos.conf


service.vgroup_mapping.service-product=default 
service.vgroup_mapping.service-order=default


3.3 初始化seata在nacos中的配置


# 初始化seata 的nacos配置
# 注意: 这里要保证nacos是已经正常运行的
cd conf
nacos-config.sh 127.0.0.1  # cmd 运行如下命令


执行成功后可以打开Nacos的控制台,在配置列表中,可以看到初始化了很多Group为SEATA_GROUP 的配置。


3.4 启动Seata

cd bin
seata-server.bat -p 9000 -m file


启动后在 Nacos 的服务列表下面可以看到一个名为 serverAddr 的服务。


四、Seata实现事务控制


4.1 初始化数据表


在我们的数据库中加入一张undo_log表,这是Seata记录事务日志要用到的表


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,
`ext` VARCHAR(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8;


4.2 添加配置


在需要进行分布式控制的微服务中进行下面几项配置:


4.2.1 pom依赖


这里两个[订单order服务,库存product服务],都需要加入这个依赖。


<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

4.2.2 DataSourceProxyConfig


Seata 是通过代理数据源实现事务分支的,所以需要配置 io.seata.rm.datasource.DataSourceProxy 的

Bean,且是 @Primary默认的数据源,否则事务不会回滚,无法实现分布式事务


这里两个[订单order服务,库存product服务]


@Configuration
public class DataSourceProxyConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
        public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }
    @Primary
    @Bean
    public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
      return new DataSourceProxy(druidDataSource);
  }
}

4.2.3 registry.conf


这里两个[订单order服务,库存product服务]的


在resources下添加Seata的配置文件 registry.conf

registry {
    type = "nacos"
    nacos {
        serverAddr = "localhost"
        namespace = "public"
        cluster = "default"
    }
}
config {
    type = "nacos"
    nacos {
        serverAddr = "localhost"
        namespace = "public"
    }
}
cluster = "default"

4.2.4 bootstrap.yml


这里两个[订单order服务,库存product服务]的


这个配置文件会优先进行读取,只要引入了nacos的conf这个依赖,一定要添加这个配置文件,否则无法注册服务。

spring:
  application:
     name: service-product和service-order
  cloud:
    nacos:
      config:
        server-addr: localhost:8848 # nacos的服务端地址
        namespace: public
        group: SEATA_GROUP
    alibaba:
      seata:
          tx-service-group: ${spring.application.name}


4.2.5 在2.2 中的order微服务开启全局事务


@GlobalTransactional//全局事务控制
public Order createOrder(Integer pid) {}


http://localhost:8091/order/prod/1

测试下单服务,会发现要 下单和库存一起成功,要么一起失败,只要出现了异常 int i = 1/0; 两者都不会写入成功。


相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
2天前
|
Nacos 数据库
分布式事务解决方案Seata
分布式事务解决方案Seata
29 1
|
2天前
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
|
2天前
|
关系型数据库 MySQL 数据库
分布式事务Seata
分布式事务Seata
|
2天前
|
存储 关系型数据库 MySQL
基于Seata实现分布式事务
通过以上步骤,你可以使用 Seata 实现分布式事务,确保在微服务架构中的事务一致性。Seata 支持多种语言和框架,能够满足不同业务场景的需求。欢迎关注威哥爱编程,一起学习成长。
|
2天前
|
Java 数据库连接 API
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
63 0
|
2天前
|
存储 Oracle 关系型数据库
分布式事物【Seata实现、下载启动Seata服务、搭建聚合父工程构建】(四)-全面详解(学习总结---从入门到深化)
分布式事物【Seata实现、下载启动Seata服务、搭建聚合父工程构建】(四)-全面详解(学习总结---从入门到深化)
49 0
|
2天前
|
开发框架 Java 数据库连接
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)(下)
分布式事物【XA强一致性分布式事务实战、Seata提供XA模式实现分布式事务】(五)-全面详解(学习总结---从入门到深化)
43 0
|
2天前
|
Windows
Windows系统下安装分布式事务组件Seata
Windows系统下安装分布式事务组件Seata
|
2天前
|
SQL 容灾 数据库
分布式事务Seata
在分布式架构系统中,服务不止一个,一个完整的业务链路肯定也不止调用一个服务,此时每个服务都有自己的数据库增删改查,而每一个写操作对应一个本地事务。如果想要确保全部的业务状态一致,也就意味着需要所有的本地事务状态一致,这在我们之前的学习中肯定是不具备的,如何做到跨服务、跨数据源的事务一致性将是本章节的重点学习内容。
30 2
|
2天前
|
SQL 数据库 Windows
SpringCloud集成seata分布式事务控制
SpringCloud集成seata分布式事务控制
19 0

热门文章

最新文章