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

一 分布式锁

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.mzph.cn/news/94476.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6 个最佳免费 Android 数据恢复软件

如果您是 Android 用户&#xff0c;您可能会发现没有回收站。然而&#xff0c;聪明的开发人员已经创建了各种 Android 数据恢复软件程序&#xff0c;可以解决各种与数据丢失相关的问题。 Android 数据恢复软件如何工作&#xff1f; 问题是当你删除一个文件时&#xff0c;它的数…

踩坑 | vue动态绑定img标签src属性的一系列报错

文章目录 踩坑 | vue项目运行后使用require()图片也不显示问题描述vue中动态设置img的src不生效问题的原因require is not defined 解决办法1&#xff1a;src属性直接传入地址解决办法2 踩坑 | vue项目运行后使用require()图片也不显示 问题描述 在网上查阅之后&#xff0c;发…

【代码随想录】LC 704. 二分查找

文章目录 前言一、题目1、原题链接2、题目描述 二、解题报告1、思路分析2、时间复杂度3、代码详解 三、知识风暴 前言 本专栏文章为《代码随想录》书籍的刷题题解以及读书笔记&#xff0c;如有侵权&#xff0c;立即删除。 一、题目 1、原题链接 704. 二分查找 2、题目描述 二…

java学生管理系统

一、项目概述 本学生管理系统旨在提供一个方便的界面&#xff0c;用于学校或机构管理学生信息&#xff0c;包括学生基本信息、课程成绩等。 二、系统架构 系统采用经典的三层架构&#xff0c;包括前端使用JavaSwing&#xff0c;后端采用Java Servlet&#xff0c;数据库使用M…

基于Java的药店管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

基于Java的医院挂号就诊系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

Spring实例化源码解析之registerBeanPostProcessors(六)

BeanPostProcessors是Spring框架中的一个扩展机制&#xff0c;它允许开发人员在Spring容器实例化、配置和初始化Bean的过程中干预和定制化。BeanPostProcessor接口定义了两个方法&#xff1a;postProcessBeforeInitialization和postProcessAfterInitialization&#xff0c;分别…

大模型 Decoder 的生成策略

本文将介绍以下内容&#xff1a; IntroductionGreedy Searchbeam searchSamplingTop-K SamplingTop-p (nucleus) sampling总结 一、Introduction 1、简介 近年来&#xff0c;由于在数百万个网页数据上训练的大型基于 Transformer 的语言模型的兴起&#xff0c;开放式语言生…

双重差分模型(DID)论文写作指南与操作手册

手册链接&#xff1a;双重差分模型&#xff08;DID&#xff09;论文写作指南与操作手册https://www.cctalk.com/m/group/90983583?xh_fshareuid60953990 简介&#xff1a; 当前&#xff0c;对于准应届生们来说&#xff0c;毕设季叠加就业季&#xff0c;写作时间显得十分宝贵…

EdgeView 4 for Mac:重新定义您的图像查看体验

您是否厌倦了那些功能繁杂、操作复杂的图像查看器&#xff1f;您是否渴望一款简单、快速且高效的工具&#xff0c;以便更轻松地浏览和管理您的图像库&#xff1f;如果答案是肯定的&#xff0c;那么EdgeView 4 for Mac将是您的理想之选&#xff01; EdgeView 4是一款专为Mac用户…

Spacewalk

Spacewalk Spacewalk是一种开源的系统管理工具&#xff0c;提供了集中管理多个Linux服务器的功能。以下是一些Spacewalk用例&#xff1a; Spacewalk是基于Substrate的parachains和Stellar之间的桥梁&#xff0c;可以实现与Stellar的资产转移。该拨款申请用于开发太空行走协议…

FFmpeg日志系统、文件与目录、操作目录

目录 FFmpeg日志系统 FFmpeg文件与目录操作 FFmpeg文件的删除与重命名 FFmpeg操作目录及list的实现 操作目录重要函数 操作目录重要结构体 FFmpeg日志系统 下面看一个简单的 demo。 #include <stdio.h> #include <libavutil/log.h>int main(int argc,char* …

讲讲项目里的仪表盘编辑器(四)分页卡和布局容器组件

讲讲两个经典布局组件的实现 ① 布局容器组件 配置面板是给用户配置布局容器背景颜色等属性。这里我们不需要关注 定义文件 规定了组件类的类型、标签、图标、默认布局属性、主文件等等。 // index.js import Container from ./container.vue; class ContainerControl extends…

ARMv8如何读取cache line中MESI 状态以及Tag信息(tag RAM dirty RAM)并以Cortex-A55示例

Cortex-A55 MESI 状态获取 一&#xff0c;系统寄存器以及读写指令二&#xff0c;Cortex-A55 Data cache的MESI信息获取&#xff08;AARCH 64&#xff09;2.1 将Set/way信息写入Data Cache Tag Read Operation Register2.2 读取Data Register 1和Data Register 0数据并解码 参考…

Linux嵌入式学习之Ubuntu入门(六)shell脚本详解

系列文章内容 Linux嵌入式学习之Ubuntu入门&#xff08;一&#xff09;基本命令、软件安装、文件结构、编辑器介绍 Linux嵌入式学习之Ubuntu入门&#xff08;二&#xff09;磁盘文件介绍及分区、格式化等 Linux嵌入式学习之Ubuntu入门&#xff08;三&#xff09;用户、用户组…

Java 基于 SpringBoot 的学生考勤系统

1 简介 本文讲解的是 Java基于 SpringBoot 的学生考勤系统。学生考勤管理系统能做到的不仅是大大简化管理员的信息管理工作&#xff0c;在提高学生考勤管理效率的同时还能缩减开支&#xff0c;更能在数字化的平面网络上将学生考勤管理最好的一面展示给客户和潜在客户&#xff…

swift加载h5页面空白

swift加载h5页面空白 problem 背景 xcode swift 项目&#xff0c;WebView方式加载h5页面本地h5地址是&#xff1a;http://localhost:5173/ 浏览器打开正常 Swift 加载h5&#xff1a; 百度官网 加载正常本地h5页面 加载空白&#xff0c;没有报错 override func viewDidLoad…

Netron【.pt转.torchscript模型展示】

Netron是一个模型的展示工具&#xff0c;它有网页版和app版&#xff1a; 网页版&#xff1a;Netron app版&#xff1a;GitHub - lutzroeder/netron: Visualizer for neural network, deep learning, and machine learning models 直接用网页版吧&#xff0c;还不用安装。 它可…

安装NodeJS并使用yarn下载前端依赖

文章目录 1、安装NodeJS1.1 下载NodeJS安装包1.2 解压并配置NodeJS1.3 验证是否安装成功2、使用yarn下载前端依赖2.1 安装yarn2.2 使用yarn下载前端依赖参考目标:在Windows下安装新版NodeJS,并使用yarn下载前端依赖,实现运行前端项目。 1、安装NodeJS 1.1 下载NodeJS安装包…

带你10分钟学会红黑树

前言&#xff1a; 我们都知道二叉搜索树&#xff0c;是一种不错的用于搜索的数据结构&#xff0c;如果二叉搜索树越接近完全二叉树&#xff0c;那么它的效率就会也高&#xff0c;但是它也存在的致命的缺陷&#xff0c;在最坏的情况下&#xff0c;二叉搜索树会退化成为单链表&am…