0
点赞
收藏
分享

微信扫一扫

Seata 的AT模式需求实战_04


接上一篇:Seata 与 Nacos Config配置中心整合_03

模拟下单场景:首先去在自己的本地创建一条下单记录,同时,还要去调用库存服务,执行减库存操作。

文章目录

  • ​​一、数据库部分​​
  • ​​1. 订单库创建​​
  • ​​2. 表结构初始化​​
  • ​​3. 库存数据库创建​​
  • ​​4. 库存表结构初始化​​
  • ​​5. 依赖新增​​
  • ​​二、订单微服务代码部分​​
  • ​​2.1. 创建实体类​​
  • ​​2.2. 创建接口类​​
  • ​​2.3. 调整控制层逻辑​​
  • ​​2.4. 修改配置文件​​
  • ​​三、库存微服务代码部分​​
  • ​​3.1. 创建实体类​​
  • ​​3.2. 接口库存Dao​​
  • ​​3.3. 容错代码​​
  • ​​3.4. 控制层逻辑调整​​
  • ​​3.5. 配置文件修改​​
  • ​​3.6. 初始化库存​​
  • ​​3.7. 容错代码简述​​
  • ​​四、测试验证​​
  • ​​4.1. 启动服务​​
  • ​​4.2. 发起第一轮请求​​
  • ​​4.3. 抛出异常​​
  • ​​4.4. 异常信息监控​​
  • ​​4.5. 流程梳理​​
  • ​​4.6. 数据库验证​​
  • ​​4.7. 发起第二轮请求​​
  • ​​4.8. 发起第三轮请求​​
  • ​​4.9. 数据库数据验证​​
  • ​​4.10. 发起第四轮请求​​
  • ​​4.11. 数据库验证​​
一、数据库部分
1. 订单库创建

新增数据库​​orderdb​​,

Seata 的AT模式需求实战_04_数据库

2. 表结构初始化

并创建订单表 和AT模式seata需要用到的undolog表

Seata 的AT模式需求实战_04_微服务_02

create table orderdb.order_tb
(
id int auto_increment
primary key,
user_id int not null,
product_id int not null
);
create table orderdb.undo_log
(
id bigint auto_increment
primary key,
branch_id bigint not null,
xid varchar(100) not null,
context varchar(128) not null,
rollback_info longblob not null,
log_status int not null,
log_created datetime not null,
log_modified datetime not null,
ext varchar(100) null,
constraint ux_undo_log
unique (xid, branch_id)
)
charset=utf8;

3. 库存数据库创建

新增数据库​​stockdb​​,并创建库存表 和AT模式seata需要用到的undolog表

Seata 的AT模式需求实战_04_spring_03

4. 库存表结构初始化

并创建库存表 和AT模式seata需要用到的undolog表

Seata 的AT模式需求实战_04_spring_04

create table stockdb.stock
(
id int auto_increment
primary key,
count int not null,
product_id int not null
);

create table stockdb.undo_log
(
id bigint auto_increment
primary key,
branch_id bigint not null,
xid varchar(100) not null,
context varchar(128) not null,
rollback_info longblob not null,
log_status int not null,
log_created datetime not null,
log_modified datetime not null,
ext varchar(100) null,
constraint ux_undo_log
unique (xid, branch_id)
)
charset=utf8;

5. 依赖新增

在parent的pom.xml中新增依赖

<!--分布式事务-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.3.0</version>
</dependency>
<!--Lombok引入-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Boot JPA 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

二、订单微服务代码部分

我们对现在的order-serv订单服务基础上调整

2.1. 创建实体类

package com.gblfy.entity;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;

@Entity
@Table(name = "order_tb")
@Data
public class Order implements Serializable {
public Order() {
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

/**
* 标题
*/
@Column(name = "product_id")
private Integer productId;

/**
* 原价格
*/
@Column(name = "user_id")
private Integer userId;

}

2.2. 创建接口类

package com.gblfy.dao;

import com.gblfy.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderResposity extends JpaRepository<Order, Integer> {
}

业务代码修改

原代码

Seata 的AT模式需求实战_04_spring_05

2.3. 调整控制层逻辑

调整后代码逻辑如下

package com.gblfy.controller;

import com.gblfy.dao.OrderResposity;
import com.gblfy.entity.Order;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class OrderController {

@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderResposity orderResposity;

//http://localhost:8000/order/create?productId=11&userId=11222
@GlobalTransactional(timeoutMills = 300000, name = "spring-cloud-demo-tx")
@GetMapping("/order/create")
public String createOrder(Integer productId, Integer userId) {

Order order = new Order();
order.setProductId(productId);
order.setUserId(userId);
orderResposity.save(order);
String result = restTemplate.getForObject("http://stock-serv/stock/reduce/" + productId, String.class);
if (!result.equals("success")) {
throw new RuntimeException();
}
return result;
}
}

2.4. 修改配置文件

server:
port: 9002
spring:
datasource:
url: jdbc:mysql://192.168.159.105:3306/orderdb
username: root
password: 123456
cloud:
nacos:
discovery:
service: order-serv
group: SEATA_GROUP
server-addr: 192.168.159.105:8848
application:
name: order-serv
seata:
enabled: true
tx-service-group: order-service
config:
type: nacos
nacos:
namespace: public
serverAddr: 192.168.159.105:8848
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
serverAddr: 192.168.159.105:8848
group: SEATA_GROUP
namespace: public
userName: "nacos"
password: "nacos"

三、库存微服务代码部分

我们对现在的stock-serv库存服务基础上调整

3.1. 创建实体类

package com.gblfy.entity;


import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "stock")
@Data
public class Stock implements Serializable {
public Stock() {
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

/**
* 标题
*/
@Column(name = "count")
private Integer count;

/**
*
*/
@Column(name = "product_id")
private Integer productId;

}

3.2. 接口库存Dao

package com.gblfy.dao;

import com.gblfy.entity.Stock;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StockResposity extends JpaRepository<Stock, Integer> {
// 通过商品ID查询库存信息
public Stock getFirstByProductId(Integer productId);
}

3.3. 容错代码

在StockApplication 中新增一个类的定义 用于获取随机的boolean值,模拟随机报错的情况

package com.gblfy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.Random;

@SpringBootApplication
public class StockApplication {

public static void main(String[] args) {
SpringApplication.run(StockApplication.class);
}
@Bean
public Random generate(){
return new Random();
}
}

3.4. 控制层逻辑调整

修改库存controller,减库存直接操作数据库

package com.gblfy.controller;

import com.gblfy.dao.StockResposity;
import com.gblfy.entity.Stock;
import io.seata.core.context.RootContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;


/**
* 库存服务
*
* @author gblfy
* @date 2021/8/19
*/
@RestController
public class StockController {
private static final Logger LOGGER = LoggerFactory.getLogger(StockController.class);

@Autowired
private StockResposity resposity;
@Autowired
private Random random;

@GetMapping("/stock/reduce/{productId}")
public String reduce(@PathVariable Integer productId) {
LOGGER.info("Storage Service Begin ... xid: " + RootContext.getXID());
if (random.nextBoolean()) {
throw new RuntimeException("this is a mock Exception");
}
Stock stock = resposity.getFirstByProductId(productId);
if (stock != null) {
stock.setCount(stock.getCount() - 1);
resposity.save(stock);
return "success";
}
return "fail";
}
}

3.5. 配置文件修改

server:
port: 8002
spring:
datasource:
url: jdbc:mysql://192.168.159.105:3306/stockdb
username: root
password: 123456
cloud:
nacos:
discovery:
service: stock-serv
group: SEATA_GROUP
server-addr: 192.168.159.105:8848
application:
name: stock-serv
seata:
enabled: true
tx-service-group: order-service
config:
type: nacos
nacos:
namespace: public
serverAddr: 192.168.159.105:8848
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
serverAddr: 192.168.159.105:8848
group: SEATA_GROUP
namespace: public
userName: "nacos"
password: "nacos"

3.6. 初始化库存

在扣库存微服务初始化数据

INSERT INTO `stock` VALUES (1, 100, 1);

3.7. 容错代码简述

这里在扣库存服务块中抛出异常,是为了模拟在创建订单库完成后,扣库存失败的场景。验证订单数据库的数据是否产生,如果没有产生从而说明分布式事务生效了,相当于跨库进行事物的回滚。

四、测试验证
4.1. 启动服务

启动订单服务

Seata 的AT模式需求实战_04_分布式事务_07


启动扣库存服务

Seata 的AT模式需求实战_04_spring_08


Seata 的AT模式需求实战_04_spring_09

4.2. 发起第一轮请求

​​http://localhost:9002/order/create?productId=11&userId=11222​​

4.3. 抛出异常

发生错误,进入咱们的容错逻辑

Seata 的AT模式需求实战_04_分布式事务_10

4.4. 异常信息监控

扣库存微服务模块

Seata 的AT模式需求实战_04_微服务_11


订单模块微服务

Seata 的AT模式需求实战_04_数据库_12

4.5. 流程梳理

①请求下单服务
②创建订单并在订单数据库中插入一条订单数据
③调用扣库存微服务
④发生异常
如果分布式事务生效的话,事务的原子性一致性应该不会产生脏数据。对吧!

4.6. 数据库验证

库存为服务发生异常后,先查看订单数据库是否产生订单数据

Seata 的AT模式需求实战_04_spring_13


再查看扣库存数据库是否扣库存成功

Seata 的AT模式需求实战_04_数据库_14


从以上截图中可以看出咱们的分布式事务生效了,既没有产生脏数据,有没有扣库存成功,符合预期。

4.7. 发起第二轮请求

再次发起请求,继续测试,由于我扣库存的数据库中只有productId=1的商品,不管我请求多少次,都会失败,脏数据不会产生扣库存也会失败
​​​http://localhost:9002/order/create?productId=11&userId=11222​​

4.8. 发起第三轮请求

把请求的地址调整成正确的路径,再次请求测试
​​​http://localhost:9002/order/create?productId=1&userId=11222​​

终于有成功的了

Seata 的AT模式需求实战_04_spring_15

4.9. 数据库数据验证

查看订单数据库是是否产生订单数据

Seata 的AT模式需求实战_04_分布式事务_16


从图中,可以看出差生了一条订单数据

在查看扣库存数据是否也减少了呢

Seata 的AT模式需求实战_04_微服务_17


从上图可以看出,产生一条订单数据,就会减少一个库存,符合咱们的预期,分布式事务也生效了。

4.10. 发起第四轮请求

继续提高并发测试,测试结果有的成功了,有的失败了。

4.11. 数据库验证

产生了5条订单数据

Seata 的AT模式需求实战_04_分布式事务_18


相应的库存也减少了5个

Seata 的AT模式需求实战_04_spring_19


测试验证到此为止!


举报

相关推荐

0 条评论