分布式事务Seata使用

news2024/12/26 23:23:12

我们要学习seata,首先需要具备如下技术储备:

  • 数据库事务的基本知识;
  • maven工具的使用;
  • 熟悉SpringCloudAlibaba技术栈;
  • 掌握SpringDataJPA简单使用;

一. Seata基本概念

1.seata是什么

Seata是阿里巴巴中间件团队发起了开源项目,其愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题,后来更名为 Seata。

Seata的设计目标是对业务无侵入,因此从业务无侵入的2PC方案着手,在传统2PC的基础上演进。它把一个分布式事务理解成一个包含了若干分支事务的全局事务。

全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个关系数据库的本地事务。

2.seata基本架构

听到这里,是不是觉得很晦涩?那么威哥通过一幅图来帮助你们进一步理解seata的架构:

通过这幅图,我们看到了seata的三个重要的组件,分别是TC TM RM。那么他们到底是什么东西呢?

  • TC:Transaction Coordinator事务协调器,管理全局的分支事务的状态,用于全局性事务的提交和回滚。
  • TM:Transaction Manager 事务管理器,用于开启、提交或者回滚全局事务。
  • RM:Resource Manager资源管理器,用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务。

3.seata执行流程

搞清楚了这几个组件的含义之后,那么seata的整个执行流程我们就可以梳理清楚了:

  • A服务的TM向TC申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID。
  • 服务的RM向TC注册分支事务,并及其纳入XID对应全局事务的管辖。
  • A服务执行分支事务,向数据库做操作。
  • A服务开始远程调用B服务,此时XID会在微服务的调用链上传播。
  • B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖。
  • B服务执行分支事务,向数据库做操作。
  • 全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚。
  • TC协调其管辖之下的所有分支事务,决定是否回滚。

二. 案例环境搭建

我们搞清楚Seata的相关概念之后,现在威哥带领大家实现一个需求:通过订单微服务实现下订单的操作,然后通知库存微服务进行库存的扣减。

1. 前期准备

我们需要先准备订单和商品实体类。

//商品
@Entity(name = "shop_product")
@Data
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer pid;//主键
    private String pname;//商品名称
    private Double pprice;//商品价格
    private Integer stock;//库存
}


//订单
@Entity(name = "shop_order")
@Data
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long oid;//订单id
    private Integer uid;//用户id
    private String username;//用户名
    private Integer pid;//商品id
    private String pname;//商品名称
    private Double pprice;//商品单价
    private Integer number;//购买数量
}

我们还需要准备项目必备的pom依赖:

这是父工程的pom.xml文件:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2. 搭建对应的微服务

现在我们分别搭建商品微服务和订单微服务

2.1 创建公共通用模块

我们创建shop-common模块,专门存放一些公共的实体类和工具类,便于其他模块进行共享。

2.1.1 在公共模块添加相关的依赖

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

然后把之前准备的实体类都拷贝到这个shop-common中来。

2.2 搭建订单微服务模块

2.2.1 添加必要依赖

取名shop-order,在这个模块里面添加相关的依赖。

<dependencies>
 
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

 
    <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>com.qf.common</groupId>
        <artifactId>springcloudAlibaba-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>com.qf.feign</groupId>
        <artifactId>springcloudAlibaba-order-product-feign</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
 
 
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>

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

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

2.2.2 编写controller

在这里,我们编写一个下单操作的服务接口。

@RestController
@RequestMapping("order")
@Slf4j
public class OrderController {

    @Autowired
    OrderService5 orderService5;

    @RequestMapping("prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid){
        return orderService5.createOrder(pid);
    }
}

2.2.3 编写service

public interface OrderService {

    Order createOrder(Integer pid);
}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Autowired
    OrderFeign orderFeign;

    @Autowired
    OrderDao orderDao;
    @Override
    public Order createOrder(Integer pid) {
        //查询指定的商品信息
        Product product = orderFeign.findProductByPid(pid);
        log.info("查询到的商品信息是:{}", JSON.toJSONString(product));
        //执行下单的操作
        Order order = new Order();
        order.setUid(1003);
        order.setUsername("测试Seata案例");
        order.setPid(pid);
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        //设置订单中的商品数量
        order.setNumber(1);
        orderDao.save(order);
        log.info("订单创建成功,订单信息是:{}",JSON.toJSONString(order));
        //执行扣减库存的操作
        orderFeign.reduceStock(pid,order.getNumber());
        return order;
    }
}

2.2.4 编写feign客户端

@FeignClient(name = "service-product")
public interface OrderFeign {

    @RequestMapping("product/{pid}")
    public Product findProductByPid(@PathVariable("pid") Integer pid);

    @RequestMapping("product/reduceStock")
    void reduceStock(@RequestParam("pid") Integer pid,@RequestParam("number") Integer number);
}

2.2.5 编写dao

public interface OrderDao extends JpaRepository<Order,Integer> {
}

2.3 搭建商品微服务模块

2.3.1 添加必要依赖

取名shop-product,在这个模块里面添加相关的依赖。

<dependencies>
 
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>com.qf.common</groupId>
        <artifactId>springcloudAlibaba-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

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

2.3.2 编写controller

@RestController
@Slf4j
@RefreshScope //配置信息的即时刷新
public class ProductController {

    @Autowired
    ProductService productService;

    //根据id查询对应的商品信息
    @RequestMapping("product/{pid}")
    public Product findProductByPid(@PathVariable("pid") Integer pid){
        Product product = productService.findProductByPid(pid);
        //JSON.toJSONString  把指定数据转换成json串
        log.info("查询到的对应的商品是:" + JSON.toJSONString(product));
        return product;
    }
 
      //扣减库存
    @RequestMapping("product/reduceStock")
    public void reduceStock(@RequestParam("pid") Integer pid,@RequestParam("number") Integer number){
        productService.reduceStock(pid,number);
    }

}

2.3.3 编写service

public interface ProductService {

    Product findProductByPid(Integer pid);

    void reduceStock(Integer pid, Integer number);
}
@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    ProductDao productDao;

    @Override
    public Product findProductByPid(Integer pid) {
        Optional<Product> optional = productDao.findById(pid);
        return optional.get();
    }

    @Override
    public void reduceStock(Integer pid, Integer number) {
        Product product = productDao.findById(pid).get();
        product.setStock(product.getStock() -  number);
        productDao.save(product);
    }
}

2.3.4 编写dao

public interface ProductDao extends JpaRepository<Product,Integer> {
}

现在我们启动测试,目前代码是没有什么问题的。但是如果我手动模拟异常。具体操作如下:

此时我们再去测试,这个时候就出现了问题了。我们发现订单能够下单成功,但是库存没有扣减。这样就出现了数据不一致的事务问题。那么我们可以使用seata来帮我们解决问题。

三. 配置使用seata

1. 下载seata

同学们可以在如下资源链接上进行下载:

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

2. 配置Seata

我们下载下来之后,会是一个压缩包。我们把这个压缩包打开之后进行相关配置。

2.1 修改registry.conf,指定seata使用nacos注册中心

registry {
  # 支持的注册中心有:file 、nacos 、eureka、redis、zk、consul、etcd3、sofa  我们使用自己的注册中心即可  所以删除其他注册中心相关的配置
  type = "nacos"

  nacos {
    serverAddr = "localhost"
    namespace = ""
    cluster = "default"
  }
 
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
    serverAddr = "localhost"
    namespace = ""
  }

}

2.2 修改nacos-config.txt,指定我们的服务名称

2.3 初始化seata在nacos中的配置

我们需要把seata相关的配置信息在nacos配置中心进行注册。

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

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

2.4 启动seata服务

切换到bin目录执行以下命令:

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

启动后在 Nacos 的服务列表下面可以看到一个名为 serverAddr的服务。如果入下图所示,小伙伴们,seata服务启动成功!!!!!

3. 使用seata进行事务控制

3.1 初始化一张数据表,用来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;

3.2 在微服务中添加seata相关的依赖

<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>

3.3 配置DataSourceProxyConfig代理数据源

Seata 是通过代理数据源实现事务分支的,所以需要配置 io.seata.rm.datasource.DataSourceProxy 的 Bean,且是 @Primary默认的数据源,否则事务不会回滚,无法实现分布式事务.

在shop-product和shop-order里面都添加如下配置类:

@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);
     }
 }
  • 在shop-product和shop-order的resources目录添加registry.conf(直接将seata里面的配置复制过来即可)。
  • 在shop-product和shop-order的resources目录添加bootstrap.yml里面,然后添加配置。

3.4 在shop-order微服务中开启全局事务

4. 结果测试

我们发生请求:http://localhost:8091/order5/prod/1

此时查看数据库,我们发现事务问题得到了控制。就是当发生异常的时候,下单的记录被回滚了,而且库存也没有出现扣减。

到现在,我们的分布式事务就得到了控制。小伙伴们,你们有没有学会呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1657350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

路由模块封装

目录 一、问题引入 二、步骤 一、问题引入 随着项目内容的不断扩大&#xff0c;路由也会越来越多&#xff0c;把所有的路由配置都堆在main.js中就不太合适了&#xff0c;所以需要将路由模块抽离出来。其好处是&#xff1a;拆分模块&#xff0c;利于维护。 二、步骤 将路由相…

【java.io.IOException: java.lang.IllegalArgumentException: db.num is null】

默认用户名&#xff1a;nacos 密码&#xff1a;nacos解决方法&#xff1a; a)在conf目录下将nacos-mysql.sql脚本创建完成&#xff1b; b)修改application.properties&#xff0c;在内容里添加如下内容 spring.datasource.platformmysql db.num1 db.url.0jdbc:mysql://localho…

videosapi开发微信管理系统

获取登录二维码&#xff1a; export interface Request {/*** 设备ID&#xff0c;首次登录传空&#xff0c;之后传接口返回的appId*/appId?: string;/*** 代理IP 格式&#xff1a;socks5://username:password123.2.2.2*/proxyIp?: string;/*** 地区*/regionId: string;[prop…

加速科技突破2.7G高速数据接口测试技术

随着显示面板分辨率的不断提升&#xff0c;显示驱动芯片&#xff08;DDIC&#xff09;的数据接口传输速率越来越高&#xff0c;MIPI、LVDS/mLVDS、HDMI等高速数据接口在DDIC上广泛应用。为满足高速数据接口的ATE测试需求&#xff0c;作为国内少数拥有完全自研的LCD Driver测试解…

Android GPU渲染屏幕绘制显示基础概念(1)

Android GPU渲染屏幕绘制显示基础概念&#xff08;1&#xff09; Android中的图像生产者OpenGL&#xff0c;Skia&#xff0c;Vulkan将绘制的数据存放在图像缓冲区中&#xff0c;Android中的图像消费SurfaceFlinger从图像缓冲区将数据取出&#xff0c;进行加工及合成。 Surface…

Tensorflow2.0笔记 - 循环神经网络RNN做IMDB评价分析

本笔记记录使用SimpleRNNCell做一个IMDB评价系统情感二分类问题的例子。 import os import time import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics, Inputos.envir…

模拟实现链表的功能

1.什么是链表&#xff1f; 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 单向或者双向 带头或者不带头 …

机器学习:基于线性回归、岭回归、xgboost回归、Lasso回归、随机森林回归预测卡路里消耗

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

小丑的身份证和复印件 (BFS + Floyd)

本题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目&#xff1a; 样例&#xff1a; 输入 2 10 (JOKERjoke #####asdr) 输出 12 思路&#xff1a; 根据题意&#xff0c;要求最短时间&#xff0c;实际上也可以理解为最短距离。 所以应该联想到有关最短距离的算法&…

【图文教程】PyCharm安装配置PyQt5+QtDesigner+PyUic+PyRcc

这里写目录标题 PyQt5、Qt Designer、PyUic、PyRcc简介&#xff08;1&#xff09;下载安装PyQt5&#xff08;2&#xff09;打开designer.exe所在位置&#xff08;3&#xff09;在PyCharm中配置QtDesigner&#xff08;4&#xff09;验证QtDesigner是否配置成功&#xff08;5&…

重学java 34.API 5.工具类

有失才有悟&#xff0c;崩塌后的重建只会更牢固 —— 24.5.9 一、System类 1.概述: 系统相关类,是一个工具类 2.特点: a.构造私有,不能利用构造方法new对象 b.方法都是静态的 3.使用: 类名直接调用 4.方法 方法 …

Linux系统入侵排查(二)

前言 为什么要做系统入侵排查 入侵排查1 1.排查历史命令记录 2.可疑端口排查 3.可疑进程排查 4.开机启动项 4.1系统运行级别示意图&#xff1a; 4.2查看运行级别命令 4.3系统默认允许级别 4.4.开机启动配置文件 入侵排查2&#xff1a; 1.启动项文件排查&#xff1…

Python从0到POC编写--实用小脚本

UrlCheck&#xff1a; 假设我们要对一份 url 列表进行访问是不是 200 &#xff0c; 量多的话肯定不能一个一个去点开看&#xff0c; 这个时候我们可以借助脚本去判断&#xff0c; 假如有一份这样的列表&#xff0c; 这份列表呢&#xff0c;奇奇怪怪&#xff0c;有些写错了…

基于Spring Boot的公司OA系统设计与实现

基于Spring Boot的银行OA系统设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 用户登录界面&#xff0c;在银行OA系统运行后&#x…

刷题第3天(中等题):LeetCode24--两两交换链表中的节点--递归法

LeetCode24&#xff1a; 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4…

FastDFS - 无法获取服务端连接资源:can‘t create connection to/xx.xx.xx.xx:0

问题描述 根据官方文档 安装完FastDFS服务器后&#xff0c; 服务正常启动&#xff0c;但是在 SpringBoot 项目使用 fastdfs-client 客户端报错无法获取服务端连接资源&#xff1a;cant create connection to/xx.xx.xx.xx:0, 一系列排查发现是获取到的 tracker 端口为 0 。 co…

Docx文件误删除如何恢复?别再花冤枉钱了,4个高效恢复软件!

不管是工作还是学习&#xff0c;总是会与各种各样的文件打交道。文件量越多就越容易出现文件丢失、文件误删的情况。遇到这些情况&#xff0c;失去的文件还能找回来吗&#xff1f;只要掌握了一些数据恢复方法&#xff0c;是很有机会恢复回来的&#xff0c;下面我会将这些方法分…

生信分析进阶2 - 利用GC含量的Loess回归矫正reads数量

在NGS数据比对后&#xff0c;需要矫正GC偏好引起的reads数量误差可用loess回归算法&#xff0c;使用R语言对封装的loess算法实现。 在NIPT中&#xff0c;GC矫正对检测结果准确性非常重要&#xff0c;具体研究参考以下文章。 Noninvasive Prenatal Diagnosis of Fetal Trisomy…

static静态成员变量和静态方法

当有new创建一个对象的,里面属性和方法,通过构造函数,能定义多个不同的对象,在我们做面向对象开发的时候,给一个场景,人在一个班级的时候,你的老师可能是固定的。 当我们用构造方法去构造的时候&#xff0c;每次都去传递一个固定的实参去定义个老师。 这样好会显得代码非常的…

DNS 解析在网络传输中有什么意义?

首先我们先说说什么是DNS解析&#xff1f; DNS解析是将域名解析为对应的IP地址的过程。DNS它作为将域名和IP地址相互映射的一个分布式数据库&#xff0c;能够使人更方便地访问互联网。DNS解析的过程就是寻找哪个IP地址对应你所输入的网址&#xff0c;然后将网页内容返回给用户…