1、减库存场景介绍:
- 1.
多线程并发安全问题
最典型的代表就是超卖现象,库存在并发量较大情况下很容易发生超卖现象,一旦发生超卖现象,就会出现多成交了订单而发不了货的情况。 - 2.场景:商品S库存余量为5时,用户A和B同时来购买一个商品,此时查询库存数都为5,库存充足则开始减库存:
用户A:update db_stock set stock = stock - 1 where id = 1
用户B:update db_stock set stock = stock - 1 where id = 1
并发情况下,更新后的结果可能是4,而实际的最终库存量应该是3才对
2、搭建减库存工程:
2.1.环境准备:
- 1.建库建表:
1001商品在001仓库有5000件库存
CREATE TABLE `db_stock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`product_code` varchar(255) DEFAULT NULL COMMENT '商品编号',
`stock_code` varchar(255) DEFAULT NULL COMMENT '仓库编号',
`count` int(11) DEFAULT NULL COMMENT '库存量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2.2.创建demo工程:
- 1.创建工程:
- 2.项目结构:
2.3.配置pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.11.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atguigu</groupId>
<artifactId>distributed-lock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>distributed-lock</name>
<description>分布式锁demo工程</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.4.配置yml:
server:
port: 6000
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://172.16.116.100:3306/test
username: root
password: root
redis:
host: 172.16.116.100
2.5.启动类:
@SpringBootApplication
@MapperScan("com.atguigu.distributedlock.mapper")
public class DistributedLockApplication {
public static void main(String[] args) {
SpringApplication.run(DistributedLockApplication.class, args);
}
}
2.6.实体类:
- 1.Stock实体类:
@Data
@TableName("db_stock")
public class Stock {
@TableId
private Long id;
private String productCode;
private String stockCode;
private Integer count;
}
- 2.StockMapper接口:
public interface StockMapper extends BaseMapper<Stock> {
}
3、编码实现减库存
- 1.编写StockController:
@RestController
public class StockController {
@Autowired
private StockService stockService;
@GetMapping("check/lock")
public String checkAndLock(){
this.stockService.checkAndLock();
return "验库存并锁库存成功!";
}
}
- 2.编写StockService:
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
public void checkAndLock() {
// 先查询库存是否充足
Stock stock = this.stockMapper.selectById(1L);
// 再减库存
if (stock != null && stock.getCount() > 0){
stock.setCount(stock.getCount() - 1);
this.stockMapper.updateById(stock);
}
}
}
-
3.测试:
-
4.查看数据库:
在浏览器中一个一个访问时,每访问一次,库存量减1,没有任何问题。
4、演示超卖现象
- 1.接下来咱们使用jmeter压力测试工具,
高并发下压测一下
,添加线程组:并发100循环50次,即5000次请求。
- 2.给线程组添加HTTP Request请求:
- 3.填写测试接口路径如下:
- 4.再选择你想要的测试报表,例如这里选择聚合报告:
- 6.启动测试,查看压力测试报告:
- 7.测试报告中各个列的含义:
项目 | Value |
---|---|
Label | 取样器别名,如果勾选Include group name ,则会添加线程组的名称作为前缀 |
# Samples | 取样器运行次数 |
Average | 请求(事务)的平均响应时间 |
Median | 中位数 |
90% Line | 90%用户响应时间 |
95% Line | 90%用户响应时间 |
99% Line | 90%用户响应时间 |
Min | 最小响应时间 |
Max | 最大响应时间 |
Error | 错误率 |
Throughput | 吞吐率 |
Received KB/sec | 每秒收到的千字节 |
Sent KB/sec | 每秒收到的千字节 |
测试结果:
请求总数5000次,平均请求时间37ms,中位数(50%)请求是在36ms内完成的,错误率0%,每秒钟平均吞吐量2568.1次。
- 8.查看mysql数据库剩余库存数:还有4870
此时如果还有人来下单,就会出现超卖现象(别人购买成功,而无货可发)。