16. Seata 分布式事务

news2024/11/15 23:19:47

Spring Cloud 微服务系列文章,点击上方合集↑

1. 简介

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。

事务是保障一系列操作要么都成功,要么都失败。就比如转账:A转账100元给B,先从A账户扣除100元,然后从B账户增加100元,假如从A账户里面已经成功扣除了100元,但是增加B账户的钱的过程中发生了异常,导致没有增加成功。这里就需要恢复A账户里面的钱(回滚)。整个转账过程就必须是事务操作。

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

2. 下载运行

可以直接下载二进制包或通过源码编译打包。

2.1 直接下载(推荐)

从官网 https://github.com/seata/seata/releases下载服务器软件包,将其解压缩。

官网下载很慢,网盘下载(推荐):「seata-server-1.6.1.zip」来自UC网盘分享
https://drive.uc.cn/s/2cfffd43e8fc4

2.2 编译安装

# 下载源码
git clone https://gitee.com/seata-io/seata.git

cd seata

# 切换分支
git checkout v1.6.1

# 编译打包
mvn -Prelease-seata -Dmaven.test.skip=true clean install -U

位置:distribution/target/seata-server-1.6.1

2.3 运行

# windows
seata-server.bat -p 8091 -h 127.0.0.1 -m file

# mac/linux
sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
  • -p 8091 指定端口
  • -h 127.0.0.1 指定地址

3. SpringCloud 集成 Seata

3.1 业务说明

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

  • 商品服务:扣除商品数量
  • 订单服务:创建订单
  • 账户服务:扣除账户余额

3.2 架构图

业务调用订单服务(创建订单)和商品服务(减少库存),订单服务再调用账户服务(减少余额)。

这个过程必须是事务性的,要么都成功,要么都失败。

3.3 创建 undo_log 表

-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
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;

undo_log表中记录相关的操作日志,主要包含被修改数据的原始值和相应的逆向操作。

3.4 创建业务表

account 账户表

  • id
  • username 用户名
  • money 余额

product 商品表

  • id
  • product_name 商品名称
  • product_number 商品数量

product_order 订单表

  • id
  • user_id 用户id
  • product_id 商品id
  • purchase_number 购买数量
  • purchase_money 购买金额

sql脚本如下:

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `money` int(11) DEFAULT 0 COMMENT '余额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `product_name` varchar(50) DEFAULT NULL COMMENT '商品名称',
  `product_number` int(11) DEFAULT 0 COMMENT '商品数量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `product_order`;
CREATE TABLE `product_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `product_id` bigint(20) DEFAULT NULL COMMENT '商品id',
  `purchase_number` int(11) DEFAULT 0 COMMENT '购买数量',
  `purchase_money` int(11) DEFAULT 0 COMMENT '购买金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `account` VALUES (1, '老王', 10000);

INSERT INTO `product` VALUES (1, '贵州茅台', 10);

  • 账户表插入一条用户信息
  • 商品表插入一条商品信息

实际情况下账户表、商品表、订单表可能在不同的数据库中,这里就模拟放在一个库里。

3.5 pom.xml

添加如下依赖包:

  • spring-boot-starter-web spring boot包依赖
  • spring-cloud-starter-alibaba-nacos-discoverynacos服务注册与发现包依赖
  • spring-cloud-starter-openfeignopenfeign服务调用依赖
  • spring-cloud-loadbalancer负载均衡包依赖
  • spring-cloud-starter-alibaba-seataseata分布式事务包依赖
  • mysql-connector-javamysql包依赖
  • spring-boot-starter-data-jpa jpa包依赖
  • lombok lombok包依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <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.cloud</groupId>
        <artifactId>spring-cloud-loadbalancer</artifactId>
    </dependency>

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

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

3.6 application.properties

  • nacos服务注册与发现地址配置
  • 数据库连接信息配置
  • seata地址、服务组等配置
# nacos
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.server-addr=http://localhost:8848
# 数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/seata_demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# seata 相关
seata.config.type=file
seata.service.grouplist.default=127.0.0.1:8091
seata.tx-service-group=test_group
seata.service.vgroup-mapping.test_group=default

启动类上要@EnableFeignClients()注解开启服务调用。

3.7 seata-product 服务

创建seata-product商品服务模块,提供decreaseNumber扣除库存数量方法。

@RestController
@RequestMapping("product")
public class ProductController {
    @Resource
    private ProductService productService;

    @GetMapping("decreaseNumber")
    public void decreaseNumber(@RequestParam Long id,
                              @RequestParam int number) {
        productService.decreaseNumber(id, number);
    }
}

public interface ProductService {

    /**
     * 减少库存数量
     */
    void decreaseNumber(Long id, int number);

}

@Service
public class ProductServiceImpl implements ProductService {

    @Resource
    private ProductRepository productRepository;

    @Override
    public void decreaseNumber(Long id, int number) {
        Product product = productRepository.getById(id);
        product.setProductNumber(product.getProductNumber() - number);
        productRepository.save(product);
    }
}

3.8 seata-account 服务

创建seata-account账户服务模块,提供decreaseMoney扣除账户余额方法。


@RestController
@RequestMapping("account")
public class AccountController {
    @Resource
    private AccountService accountService;

    @GetMapping("decreaseMoney")
    public void decreaseMoney(@RequestParam Long userId,
                              @RequestParam int money) {
        accountService.decreaseMoney(userId, money);
    }
}

public interface AccountService {

    /**
     * 减少用户余额
     */
    void decreaseMoney(Long userId, int money);

}

@Service
public class AccountServiceImpl implements AccountService {

    @Resource
    private AccountRepository accountRepository;


    @Override
    public void decreaseMoney(Long userId, int money) {
        Account account = accountRepository.getById(userId);
        account.setMoney(account.getMoney() - money);
        accountRepository.save(account);
    }
}

3.9 seata-order 服务

创建seata-order订单服务模块,提供createOrder创建订单方法,它远程调用了账户服务decreaseMoney扣除余额方法。

@RestController
@RequestMapping("order")
public class OrderController {
    @Resource
    private OrderService orderService;

    @GetMapping("createOrder")
    public void createOrder(@RequestParam Long userId,
                            @RequestParam Long productId,
                            @RequestParam int number,
                            @RequestParam int money) {
        orderService.createOrder(userId, productId, number, money);
    }
}

public interface OrderService {

    /**
     * 创建订单
     */
    void createOrder(Long userId, Long productId, int number, int money);

}


@Service
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderRepository orderRepository;

    @Resource
    private AccountService accountService;

    @Override
    public void createOrder(Long userId, Long productId, int number, int money) {
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setPurchaseNumber(number);
        order.setPurchaseMoney(money);
        // 创建订单
        orderRepository.save(order);

        // 调用账户服务 扣除账户余额
        accountService.decreaseMoney(userId, money);
    }
}

@FeignClient(name = "seata-account")
public interface AccountService {

    @GetMapping("/account/decreaseMoney")
    void decreaseMoney(@RequestParam Long userId,
                       @RequestParam int money);
}

3.10 seata-business

创建seata-business业务服务模块,提供purchase购买商品方法,它远程调用了订单服务createOrder创建订单方法和商品服务decreaseNumber扣除库存数量方法。

purchase()方法加上@GlobalTransactional注解开启事务,当有异常发生时会进行回滚,这里通过int i = 1 / 0 ;模拟异常。

@RestController
@RequestMapping("business")
public class BusinessController {

    @Resource
    private BusinessService businessService;

    @GetMapping("purchase")
    public String purchase() {
        businessService.purchase();
        return "操作成功";
    }
}

public interface BusinessService {
    void purchase();
}

@Service
public class BusinessServiceImpl implements BusinessService {

    @Resource
    private ProductService productService;

    @Resource
    private OrderService orderService;

    @GlobalTransactional
    @Override
    public void purchase() {
        // 调用订单服务创建订单
        orderService.createOrder(1L, 1L, 1, 1499);
        // 模拟异常
        int i = 1 / 0 ;
        // 调用商品服务扣除库存
        productService.decreaseNumber(1L, 1);
    }
}

// 远程Order服务接口
@FeignClient(name = "seata-order")
public interface OrderService {

    @GetMapping("/order/createOrder")
    void createOrder(@RequestParam Long userId,
                     @RequestParam Long productId,
                     @RequestParam int number,
                     @RequestParam int money);
}

// 远程product服务接口
@FeignClient(name = "seata-product")
public interface ProductService {

    @GetMapping("/product/decreaseNumber")
     void decreaseNumber(@RequestParam Long id,
                               @RequestParam int number);
}

3.11 测试

访问接口地址:http://localhost:8304/business/purchase

BusinessServiceImpl#purchase方法不加@GlobalTransactional注解:订单表已经创建新的订单并且账户余额已扣除,但是商品库存数量没有减少。

BusinessServiceImpl#purchase方法加上@GlobalTransactional注解:订单表和账号表会回滚,也就是订单记录和账户余额都没有发生变化。

这里只在seata-business服务模拟发生异常,实际上不管在那个服务上发生异常(订单服务、商品服务、账户服务),数据都会回滚到之前的状态。

Seata将被修改数据的原始值和相应的逆向操作记录在undo_log表中,如果发生异常通过undo_log表中的内容进行回滚,我们可以通过调试模式打个断点,然后去查看数据库的undo_log表就可以查看到相关数据。

  • 在事务方法执行过程中会新增undo_log表数据记录,并在事务方法执行结束后清除记录,所以只能通过断点去查看。

4. 结语

本文通过用户购买商品的案例来使用Seata分布式事务:创建订单、扣除余额、减少库存,可以看出通过Seata使用分布式事务非常的简单方便,只需要一个@GlobalTransactional注解。


Spring Cloud 微服务系列 完整的代码在仓库的sourcecode/spring-cloud-demo目录下。

gitee(推荐):https://gitee.com/cunzaizhe/xiaohuge-blog

github:https://github.com/tigerleeli/xiaohuge-blog

关注微信公众号:“小虎哥的技术博客”,让我们一起成为更优秀的程序员❤️!

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

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

相关文章

sentinel-dashboard-1.8.0.jar开机自启动脚本

启动阿里巴巴的流控组件控制面板需要运行一个jar包&#xff0c;通常需要运行如下命令&#xff1a; java -server -Xms4G -Xmx4G -Dserver.port8080 -Dcsp.sentinel.dashboard.server127.0.0.1:8080 -Dproject.namesentinel-dashboard -jar sentinel-dashboard-1.8.0.jar &…

如何评估测试用例的优先级?

评估测试用例的优先级&#xff0c;有助于我们及早发现和解决可能对系统稳定性和功能完整性产生重大影响的问题&#xff0c;助于提高测试质量&#xff0c;提高用户满意度。 如果没有做好测试用例的优先级评估&#xff0c;往往容易造成对系统关键功能和高风险场景测试的忽略&…

STM32定时器

目录 基本定时器结构框图 通用定时器结构框图 高级定时器结构框图 ​编辑TIMx时基单元 定时工作原理 影子寄存器 ​编辑 定时器中断基本结构 定时器计时中断 定时器外部中断 输出比较 OC 输出比较模式 PWM基本结构 输出比较常用函数 使用PWM来驱动舵机 输入捕…

NAS文件的名称或路径过长导致文件同步被挂起

将文件复制到群晖设备时遇到文件名长度限制问题&#xff0c;NTFS文件系统&#xff08;通过Samba等方式在群晖上使用&#xff09;: 在Windows系统上广泛使用的NTFS文件系统也支持较长的文件名&#xff0c;最大长度为255个字符。然而&#xff0c;要注意的是&#xff0c;使用Samba…

如何找回回收站删除的文件?文件恢复,3个方法!

“求助求助&#xff01;回收站里面删除的文件还能恢复吗&#xff1f;在清理电脑内存的时候一不小心把回收站清空了&#xff0c;现在不知道如何是好&#xff0c;请大家帮帮我&#xff01;” 电脑回收站里的文件清空了就是被永久删除了吗&#xff1f;如果误删了回收站里的文件还有…

成都优优聚是做美团餐饮代运营的吗?

成都优优聚公司是一家专注于美团代运营的企业&#xff0c;致力于为餐饮业主提供全方位的服务和解决方案。在如今的互联网时代&#xff0c;美团已经成为了许多餐饮业主不可或缺的平台之一&#xff0c;但是对于一些传统的餐饮业主来说&#xff0c;运营美团平台可能并不容易&#…

直播软件开发趋势揭秘:抓住行业热点实现爆发增长

直播软件开发者们正迎来一个前所未有的繁荣时期。随着社交媒体的普及和5G网络的迅猛发展&#xff0c;直播行业吸引了越来越多的用户&#xff0c;创造了巨大的商机。本文将揭示最新的直播软件开发趋势&#xff0c;帮助你抓住这个行业的热点&#xff0c;实现爆发性的增长。 关键…

【广州华锐互动】屠宰场生猪检疫VR模拟演练系统

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术在各个领域的应用越来越广泛。在教育领域&#xff0c;VR技术也为学生提供了更加真实和沉浸式的学习体验。屠宰场生猪检疫VR模拟演练系统由VR公司广州华锐互动所开发&#xff0c;作为一种新型的教学方式&a…

黑马VUE3视频笔记

目录 一、使用create-vue创建项目 二、setup选项 三、reactive和ref函数 1.reactive() 2.ref() 三、computed 四、watch ​五、生命周期函数 六、父传子、子传父 父传子defineProps 子传父defineEmits 七、模板引用 ref defineExpose 八、跨层传递普通数据 prov…

正交对角化,奇异值分解

与普通矩阵对角化不同的是&#xff0c;正交对角化是使用正交矩阵对角化&#xff0c;正交矩阵是每列向量都是单位向量&#xff0c;正交矩阵*它的转置就是单位矩阵 与普通矩阵对角化一样&#xff0c;正交对角化的结果也是由特征值组成的对角矩阵 本质还是特征向量对原矩阵的拉伸…

Go 里的超时控制

前言 日常开发中我们大概率会遇到超时控制的场景&#xff0c;比如一个批量耗时任务、网络请求等&#xff1b;一个良好的超时控制可以有效的避免一些问题&#xff08;比如 goroutine 泄露、资源不释放等&#xff09;。 Timer 在 go 中实现超时控制的方法非常简单&#xff0c;…

【3】贪心算法-最优装载问题-加勒比海盗

算法背景 在北美洲东南部&#xff0c;有一片神秘的海域&#xff0c;那里碧海蓝天、阳光 明媚&#xff0c;这正是传说中海盗最活跃的加勒比海&#xff08;Caribbean Sea&#xff09;。 有一天&#xff0c;海盗们截获了一艘装满各种各样古董的货船&#xff0c;每一 件古董都价值连…

python time和datetime的常用转换处理

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 &#x1f447; &#x1f447; &#x1f447; 更多精彩机密、教程&#xff0c;尽在下方&#xff0c;赶紧点击了解吧~ python源码、视频教程、插件安装教程、资料我都准备好了&#xff0c;直接在文末名片自取就可 一、time 1、…

解决前端二进制流下载的文件(例如:excel)打不开的问题

1. 现在后端请求数据后&#xff0c;返回了一个二进制的数据&#xff0c;我们要把它下载下来。 这是响应的数据&#xff1a; 2. 这是调用接口的地方&#xff1a; uploadOk(){if(this.files.length 0){return this.$Message.warning("请选择上传文件&#xff01;&#xff…

2023-09-28 monetdb-databae的概念和作用-分析

摘要: 每个数据库对于db,schema以及user,role都有一套自己的设计, 不同数据库间对于相同名字的东西例如database和schema可以说南辕北辙, 例如mysql中schema其实是database的同义词. 本文分析monetdb的database的概念和作用 备份: https://stoneatom.yuque.com/staff-ft8n1u…

Android AMS——APP启动流程(三)

Android 应用启动方式主要有两种 , 冷启动和热启动。 冷启动:后台没有应用进程 , 需要先创建进程 , 然后启动 Activity ;热启动:后台有应用进程 , 不创建进程 , 直接启动 Activity ; 其实,还有一种温起动的方式,就是用户按了返回键退出应用,随后又从新启动,可是活…

JS三大运行时全面对比:Node.js vs Bun vs Deno

全文约 5100 字&#xff0c;预计阅读需要 15 分钟。 JavaScript 运行时是指执行 JavaScript 代码的环境。目前&#xff0c;JavaScript 生态中有三大运行时&#xff1a;Node.js、Bun、Deno。老牌运行时 Node.js 的霸主地位正受到 Deno 和 Bun 的挑战&#xff0c;下面就来看看这…

分析几道关于死锁的真题

以下四点是408中死锁这一节的内容&#xff0c;考频基本上为每年一道题&#xff0c;虽然一半以上的题目是关于银行家算法的不安全序列的送分题&#xff0c;但其中有几道题综合了这以下几个概念&#xff0c;如果现在不提前分析这几个概念之间的关系的话。可能考场上就是随便蒙一个…

华为云云耀云服务器L实例评测 | 实例使用教学之简单使用:通过命令行管理华为云云耀云服务器

华为云云耀云服务器L实例评测 &#xff5c; 实例使用教学之简单使用&#xff1a;通过命令行管理华为云云耀云服务器 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀云服务…

postgresql|数据库|数据库测试工具pgbench之使用

前言&#xff1a; 数据库是项目中的重要组件&#xff0c;也是一个基础的重要组件&#xff0c;其地位说是第一我想应该是没有什么太多问题的。 那么&#xff0c;数据库的设计这些方面是不用多说的&#xff0c;关键的第一步&#xff0c;主要是涉及数据库的部署方式&#xff0c;…