分布式应用下事务的解决方案之一:SpringBoot+Alibaba-Seata简单应用

本案例来自于 B站尚硅谷 视频
分布式微服务的事务处理非常重要,由于本人刚入行,其实现原理尚不清楚,本文只是介绍如何简单的体验一下seata

相关代码已经上传Github,需要可自行download
https://github.com/DouGuangJi/Seata/tree/master

1、环境准备:

Maven:3.6.1
数据库:Mysql
Nacos:1.1.3
Seata:1.0.0
操作系统: win10
开发工具:Idea 2019.3
JDK:1.8

2、Seata 官网:

官网:http://seata.io/zh-cn/index.html
Github:https://github.com/seata/seata

3、Seata 简介

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

相关术语:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

4、Seata-Server 安装:

4.1、下载解压

​ 下载地址:http://seata.io/zh-cn/blog/download.html
​ 解压后目录如下

4.2、修改配置

建议修改之前养成备份文件的好习惯。

打开conf下file.conf做如下修改:

打开conf 下 registry.conf 做如下修改:

4.3、去自己的数据库建库建表

库名:seata

表sql:https://github.com/seata/seata/blob/1.0.0/script/server/db/mysql.sql

5、启动nacos

......................................................................

6、启动seata

​ bin/seata-server.bat 双击即可

​ 完成后可以看到 服务已经注册到nacos上

7、业务说明

我们创建三个服务,一个订单服务,一个库存服务、一个账户服务

当客户下单,会在订单服务创建一个订单,然后远程调用库存服务来扣减下单商品的库存,再通过远程调账户服务来扣减用户账户中的余额,最后在订单服务中修改订单状态为已经完成。

该操作跨越三个库,有两次远程调用,可以演示分布式事务。

8、建库SQL

--存储订单的库

CREATE DATABASE seata_order;

--存储库存的库

CREATE DATABASE seata_storage;

--存储账户信息的库

CREATE DATABASE seata_account;

9、建表SQL

--在seata_order下新建:

CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`count` INT(11) DEFAULT NULL COMMENT '数量',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结'
 ) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;SELECT * FROM t_order;

--在seata_storage 下新建:

CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)
VALUES('1','1','100','0','100');

--在seata_account新建:

CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES('1','1','1000','0','1000')

然后分别在这三个库下建seata的日志表:
db_undo_log.sql 如下:

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;

数据库如下图所示:

10、Idea新建工程

​ 采用父工程pom聚合方式构建
​ 在父工程下新建三个module
​ Cloud-Alibaba-Seata-OrderService-2001
​ Cloud-Alibaba-Seata-StorageService-2002
​ Cloud-Alibaba-Seata-AccountService-2003

  相关代码已经上传Github,需要可自行download
  https://github.com/DouGuangJi/Seata/tree/master

  贴部分代码(order模块):
  @Service
  @Slf4j
  public class OrderService {
      @Autowired
      private OrderDao orderDao;
      @Autowired
      private StorageService storageService;
      @Autowired
      private AccountService accountService;
 /**
 * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
 */
  //切记,第一次启动将该注解注释调
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order){
    log.info("----->开始新建订单");
    //新建订单,调用本服务接口
    orderDao.create(order);
    //扣减库存,通过openFeign远程调用库存服务
    log.error("----->订单微服务开始调用库存,做扣减Count");
    storageService.decrease(order.getProductId(),order.getCount());
    log.error("----->订单微服务开始调用库存,做扣减end");
    //扣减账户。通过openFeign远程调用账户服务
    log.error("----->订单微服务开始调用账户,做扣减Money");
    accountService.decrease(order.getUserId(),order.getMoney());
    log.error("----->订单微服务开始调用账户,做扣减end");
    //修改订单状态,从零到1代表已经完成,调用本服务
    log.error("----->修改订单状态开始");
    orderDao.update(order.getUserId(),0);
    log.error("----->修改订单状态结束");
    log.error("----->下订单结束了");
}}

​ controller:

  @RestController
  @Slf4j
  public class OrderController {
      @Resource
      private OrderService orderService;
      @GetMapping("/order/create")
      public CommonResult create(Order order) {
          orderService.create(order);
          return new CommonResult(200,"订单创建成功");
      }}

​ 主启动类:

  @EnableDiscoveryClient
  @EnableFeignClients
  /**
   * 取消数据源自动创建的配置
   */
  @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
  public class SeataOrderApplication {
     public static void main(String[] args) {
     SpringApplication.run(SeataOrderApplication.class, args);
  }}

11、测试

### 11.1、三个模块启动之后,GET请求 :(注意,此时的测试是没有异常的,业务顺利执行完成,对应看到表数据)

http://localhost:2001/order/create?userId=1&productId=1&count=1&money=100
​ 该请求首先会在t_order 表中创建一个订单

​ 然后在t_storage表处理库存

​ 然后在t_account表处理账户

​ 最后在t_order修改订单状态(t_order中的status已经变为1)
### 11.2、模拟异常,openFeign超时异常,在account 模块模拟

​ 此时我们再去请求:
http://localhost:2001/order/create?userId=1&productId=1&count=1&money=100,返回超时异常
​ 我们的需求是:发生异常,要保证数据的一致性。
​ 此时再去数据库看表,发现数据出现问题,订单创建了,库存减少了,账户没扣钱,状态未改变,此时就出现了分布式事务问题

11.3、解决方案:

​ 此时我们须在我们order服务加:

​ 然后再去请求:
http://localhost:2001/order/create?userId=1&productId=1&count=1&money=100
​ 会发现返回超时异常,但数据没有改变,也解决了我们的分布式事务问题。

​ 至此seata的最基本最简单应用ok,但是seata的学习还远远未结束,如此优秀的框架,很值得我们去深入学习,搞明白其原理之时,回来继续完善。

posted @ 2020-10-22 11:27  一个努力的人QAQ  阅读(469)  评论(0编辑  收藏  举报