接上一篇: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
,
2. 表结构初始化
并创建订单表 和AT模式seata需要用到的undolog表
create table orderdb.order_tb
(id int auto_incrementprimary key,user_id int not null,product_id int not null
);
create table orderdb.undo_log
(id bigint auto_incrementprimary 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_logunique (xid, branch_id)
)
charset=utf8;
3. 库存数据库创建
新增数据库stockdb
,并创建库存表 和AT模式seata需要用到的undolog表
4. 库存表结构初始化
并创建库存表 和AT模式seata需要用到的undolog表
create table stockdb.stock
(id int auto_incrementprimary key,count int not null,product_id int not null
);create table stockdb.undo_log
(id bigint auto_incrementprimary 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_logunique (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> {
}
业务代码修改
原代码
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 {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate 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/orderdbusername: rootpassword: 123456cloud:nacos:discovery:service: order-servgroup: SEATA_GROUPserver-addr: 192.168.159.105:8848application:name: order-serv
seata:enabled: truetx-service-group: order-serviceconfig:type: nacosnacos:namespace: publicserverAddr: 192.168.159.105:8848group: SEATA_GROUPuserName: "nacos"password: "nacos"registry:type: nacosnacos:application: seata-serverserverAddr: 192.168.159.105:8848group: SEATA_GROUPnamespace: publicuserName: "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);}@Beanpublic 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);@Autowiredprivate StockResposity resposity;@Autowiredprivate 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/stockdbusername: rootpassword: 123456cloud:nacos:discovery:service: stock-servgroup: SEATA_GROUPserver-addr: 192.168.159.105:8848application:name: stock-serv
seata:enabled: truetx-service-group: order-serviceconfig:type: nacosnacos:namespace: publicserverAddr: 192.168.159.105:8848group: SEATA_GROUPuserName: "nacos"password: "nacos"registry:type: nacosnacos:application: seata-serverserverAddr: 192.168.159.105:8848group: SEATA_GROUPnamespace: publicuserName: "nacos"password: "nacos"
tx-service-group:的值从nacos这里取,必须保持一致
3.6. 初始化库存
在扣库存微服务初始化数据
INSERT INTO `stock` VALUES (1, 100, 1);
3.7. 容错代码简述
这里在扣库存服务块中抛出异常,是为了模拟在创建订单库完成后,扣库存失败的场景。验证订单数据库的数据是否产生,如果没有产生从而说明分布式事务生效了,相当于跨库进行事物的回滚。
四、测试验证
4.1. 启动服务
启动订单服务
启动扣库存服务
4.2. 发起第一轮请求
http://localhost:9002/order/create?productId=11&userId=11222
4.3. 抛出异常
发生错误,进入咱们的容错逻辑
4.4. 异常信息监控
扣库存微服务模块
订单模块微服务
4.5. 流程梳理
①请求下单服务
②创建订单并在订单数据库中插入一条订单数据
③调用扣库存微服务
④发生异常
如果分布式事务生效的话,事务的原子性一致性应该不会产生脏数据。对吧!
4.6. 数据库验证
库存为服务发生异常后,先查看订单数据库是否产生订单数据
再查看扣库存数据库是否扣库存成功
从以上截图中可以看出咱们的分布式事务生效了,既没有产生脏数据,有没有扣库存成功,符合预期。
4.7. 发起第二轮请求
再次发起请求,继续测试,由于我扣库存的数据库中只有productId=1的商品,不管我请求多少次,都会失败,脏数据不会产生扣库存也会失败
http://localhost:9002/order/create?productId=11&userId=11222
4.8. 发起第三轮请求
把请求的地址调整成正确的路径,再次请求测试
http://localhost:9002/order/create?productId=1&userId=11222
终于有成功的了
4.9. 数据库数据验证
查看订单数据库是是否产生订单数据
从图中,可以看出差生了一条订单数据
在查看扣库存数据是否也减少了呢
从上图可以看出,产生一条订单数据,就会减少一个库存,符合咱们的预期,分布式事务也生效了。
4.10. 发起第四轮请求
继续提高并发测试,测试结果有的成功了,有的失败了。
4.11. 数据库验证
产生了5条订单数据
相应的库存也减少了5个
测试验证到此为止!