分布式锁:四种方案解决商品超卖的方案

2023-10-05 02:04

本文主要是介绍分布式锁:四种方案解决商品超卖的方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一 分布式锁

1.1 分布式锁的作用

在多线程高并发场景下,为了保证资源的线程安全问题,jdk为我们提供了synchronized关键字和ReentrantLock可重入锁,但是它们只能保证一个工程内的线程安全。在分布式集群、微服务、云原生横行的当下,如何保证不同进程、不同服务、不同机器的线程安全问题。jdk并没有给我们提供既有的解决方案。需要自己通过编写方案来解决,目前主流的实现有以下方式:

  1. 基于mysql关系型实现

  2. 基于redis非关系型数据实现

  3. 基于zookeeper/etcd实现

二  模拟单体超卖

2.1 工程结构

2.2 编写工程代码

1.pom文件

  <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.1</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><version>2.3.12.RELEASE</version></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>

2.配置文件

server.port=9999
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/fenbu_lock?characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=cloudiip
redis.host=172.16.116.100

 3.controller

@RestController
public class StockController {@Autowiredprivate StockService stockService;@GetMapping("stock/deduct")public String deduct(){// this.stockService.deduct();this.stockService.deductByMsqlDb();return "hello stock deduct!!";}}

4.service

@Service
public class StockService {@Autowiredprivate StockMapper stockMapper;private Stock stock = new Stock();private ReentrantLock lock = new ReentrantLock();public void deduct(){
//        lock.lock();
//        try {
//            stock.setStock(stock.getStock() - 1);
//            System.out.println("库存余量:" + stock.getStock());
//        } finally {
//            lock.unlock();
//        }}public void deductByMsqlDb(){// 先查询库存是否充足Stock stock = this.stockMapper.selectById(1L);// 再减库存if (stock != null && stock.getCount() > 0){stock.setCount(stock.getCount() - 1);this.stockMapper.updateById(stock);}}}

5.mapper


@Mapper
public interface StockMapper extends BaseMapper<Stock> {
}

6.启动类

@SpringBootApplication
@MapperScan("com.atguigu.distributed.lock.mapper")
public class DistributedLockApplication {public static void main(String[] args){SpringApplication.run(DistributedLockApplication.class, args);System.out.println("========================启动成功==========");}}

7.pojo类

@Data
@TableName("db_stock")
public class Stock {@TableIdprivate Long id;private String productCode;private String stockCode;private Integer count;// private Integer stock = 5000;
}

8.附件数据表

1.新建一个数据库,附件数据表,如图

2.脚本文件

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.3 测试验证

http://localhost:9999/stock/deduct

查看数据库

2.4 jmeter模拟并发访问

2.4.1 启动jmeter

 2.4.2 配置jmeter

1.添加线程组

并发100循环50次,即5000次请求。  

3.给线程组添加HTTP Request请求:

4.将接口地址:http://localhost:9999/stock/deduct  配置到下面

 5.再选择你想要的测试报表,例如这里选择聚合报告:

启动测试,查看压力测试报告:

 参数api说明如下:

1.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次,平均请求时间129ms,中位数(50%)请求是在36ms内完成的,错误率0%,每秒钟平均吞吐量716.7次。

结论:此时如果还有人来下单,就会出现超卖现象(别人购买成功,而无货可发)。

三 方案1:使用jvm的本地锁解决冲突

3.1 原理

添加synchronized关键字之后,StockService就具备了对象锁,由于添加了独占的排他锁,同一时刻只有一个请求能够获取到锁,并减库存。此时,所有请求只会one-by-one执行下去,也就不会发生超卖现象。

3.2 操作

用jvm锁(synchronized关键字或者ReetrantLock)试试:

2.使用jmeter再次测试

查看数据库

并没有发生超卖现象,完美解决。  

3.3 此方案的缺点失效情况

1.多例模式  2.事务 ;3.集群

四 方案2:使用update的sql解决本地锁

4.1 操作案例

1.mapper级别

    @Update("update db_stock set count=count-#{count} where product_code=#{productCode} and count>=#{count}")int updateStock(@Param("productCode") String productCode,@Param("count") Integer count);

2.service

  public   void deductBySql(){// 先查询库存是否充足this.stockMapper.updateStock("1001",1);System.out.println("请求进来了.......");}

 3.查看数据库

4.并发压力测试

5.查看效果:均正确消费。

 

4.2 此方案缺点

优点:能够解决jvm本地锁多失效的3种情况。

缺点:1.确定锁的范围 行级锁还是表级锁;2.同一个商品有多条库存记录;3.无法记录库存前后的变化记录。

五  解决单体超卖的其他方式

3.1 使用悲观锁和乐观锁

除了使用jvm锁之外,还可以使用数据锁:悲观锁 或者 乐观锁。

一个sql:直接更新时判断,在更新中判断库存是否大于0 ;

update table set surplus = (surplus - buyQuantity) where id = 1 and (surplus - buyQuantity) > 0

1.悲观锁:在读取数据时锁住那几行,其他对这几行的更新需要等到悲观锁结束时才能继续 。

select ... for update

2.乐观锁:读取数据时不锁,更新时检查是否数据已经被更新过,如果是则取消当前更新进行重试。

version 或者 时间戳(CAS思想)。

这篇关于分布式锁:四种方案解决商品超卖的方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/3165

相关文章

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

解决RocketMQ的幂等性问题

《解决RocketMQ的幂等性问题》重复消费因调用链路长、消息发送超时或消费者故障导致,通过生产者消息查询、Redis缓存及消费者唯一主键可以确保幂等性,避免重复处理,本文主要介绍了解决RocketM... 目录造成重复消费的原因解决方法生产者端消费者端代码实现造成重复消费的原因当系统的调用链路比较长的时

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

SpringBoot监控API请求耗时的6中解决解决方案

《SpringBoot监控API请求耗时的6中解决解决方案》本文介绍SpringBoot中记录API请求耗时的6种方案,包括手动埋点、AOP切面、拦截器、Filter、事件监听、Micrometer+... 目录1. 简介2.实战案例2.1 手动记录2.2 自定义AOP记录2.3 拦截器技术2.4 使用Fi

kkFileView启动报错:报错2003端口占用的问题及解决

《kkFileView启动报错:报错2003端口占用的问题及解决》kkFileView启动报错因office组件2003端口未关闭,解决:查杀占用端口的进程,终止Java进程,使用shutdown.s... 目录原因解决总结kkFileViewjavascript启动报错启动office组件失败,请检查of

Python获取浏览器Cookies的四种方式小结

《Python获取浏览器Cookies的四种方式小结》在进行Web应用程序测试和开发时,获取浏览器Cookies是一项重要任务,本文我们介绍四种用Python获取浏览器Cookies的方式,具有一定的... 目录什么是 Cookie?1.使用Selenium库获取浏览器Cookies2.使用浏览器开发者工具

SQL Server安装时候没有中文选项的解决方法

《SQLServer安装时候没有中文选项的解决方法》用户安装SQLServer时界面全英文,无中文选项,通过修改安装设置中的国家或地区为中文中国,重启安装程序后界面恢复中文,解决了问题,对SQLSe... 你是不是在安装SQL Server时候发现安装界面和别人不同,并且无论如何都没有中文选项?这个问题也