热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

redisrua解决库存问题_通过乐观锁解决库存超卖的问题

前言在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能。库存超卖问题是有很多种技术解决方案的,比如悲观锁&#
0097369deec67fd4cb0aedceca495624.png

前言

在通过多线程来解决高并发的问题上,线程安全往往是最先需要考虑的问题,其次才是性能。库存超卖问题是有很多种技术解决方案的,比如悲观锁,分布式锁,乐观锁,队列串行化,Redis原子操作等。本篇通过MySQL乐观锁来演示基本实现。

开发前准备

1. 环境参数

  • 开发工具:IDEA
  • 基础工具:Maven+JDK8
  • 所用技术:SpringBoot+Mybatis
  • 数据库:MySQL5.7
  • SpringBoot版本:2.2.5.RELEASE

2. 创建数据库

基本的scheme已建好,演示就拿最简单的数据结构最好不过了。

DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品id',`name` varchar(30) DEFAULT NULL COMMENT '商品名称',`stock` int(11) DEFAULT '0' COMMENT '商品库存',`version` int(11) DEFAULT '0' COMMENT '并发版本控制',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '商品表';INSERT INTO `goods` VALUES (1, 'iphone', 10, 0);
INSERT INTO `goods` VALUES (2, 'huawei', 10, 0);DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (`id` int(11) AUTO_INCREMENT,`uid` int(11) COMMENT '用户id',`gid` int(11) COMMENT '商品id',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '订单表';

没有环境的小伙伴可以通过Docker实战之MySQL主从复制,快速的进行MySQL环境的搭建。创建数据库test,然后导入相关的sql初始化Table。

3. 配置 pom 文件中的相关依赖

下边是pom.xml依赖配置。

org.springframework.bootspring-boot-starter-weborg.mybatis.spring.bootmybatis-spring-boot-starter2.1.1org.springframework.bootspring-boot-devtoolsruntimetruemysqlmysql-connector-javaruntimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtest

4. 配置 application.yml

由于演示中MyBatis基于接口映射,配置简单。application.yml中只需要配置mysql相关即可

spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3307/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTCusername: rootpassword: root

5. 创建相关Bean

package com.idcmind.ants.entity;public class Goods {private int id;private String name;private int stock;private int version;...此处省略getter、setter以及 toString方法
}
public class Order {private int id;private int uid;private int gid;...此处省略getter、setter以及 toString方法
}

乐观锁解决库存超卖方案

1. Dao层开发

GoodsDao.java

@Mapper
public interface GoodsDao {/*** 查询商品库存* @param id 商品id* @return*/@Select("SELECT * FROM goods WHERE id = #{id}")Goods getStock(@Param("id") int id);/*** 乐观锁方案扣减库存* @param id 商品id* @param version 版本号* @return*/@Update("UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = #{id} AND stock > 0 AND version = #{version}")int decreaseStockForVersion(@Param("id") int id, @Param("version") int version);
}

OrderDao.java

这里需要特别注意,由于order是sql中的关键字,所以表名需要加上反引号。

@Mapper
public interface OrderDao {/*** 插入订单* 注意: order表是关键字,需要`order`* @param order*/@Insert("INSERT INTO `order` (uid, gid) VALUES (#{uid}, #{gid})")@Options(useGeneratedKeys = true, keyProperty = "id")int insertOrder(Order order);
}

2. Service层开发

GoodsService.java

@Service
public class GoodsService {@Autowiredprivate GoodsDao goodsDao;@Autowiredprivate OrderDao orderDao;/*** 扣减库存* @param gid 商品id* @param uid 用户id* @return SUCCESS 1 FAILURE 0*/@Transactionalpublic int sellGoods(int gid, int uid) {// 获取库存Goods goods = goodsDao.getStock(gid);if (goods.getStock() > 0) {// 乐观锁更新库存int update = goodsDao.decreaseStockForVersion(gid, goods.getVersion());// 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回if (update == 0) {return 0;}// 库存扣减成功,生成订单Order order = new Order();order.setUid(uid);order.setGid(gid);int result = orderDao.insertOrder(order);return result;}// 失败返回return 0;}
}

并发测试

这里我们写个单元测试进行并发测试。

@SpringBootTest
class GoodsServiceTest {@AutowiredGoodsService goodsService;@Testvoid seckill() throws InterruptedException {// 库存初始化为10,这里通过CountDownLatch和线程池模拟100个并发int threadTotal = 100;ExecutorService executorService = Executors.newCachedThreadPool();final CountDownLatch countDownLatch = new CountDownLatch(threadTotal);for (int i = 0; i {try {goodsService.sellGoods(1, uid);} catch (Exception e) {e.printStackTrace();}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();}
}

查看数据库验证是否超卖

5e3317aca193274e0c7755848d320089.png

上图的结果与我们的预期一致。此外还可以通过Postman或者Jmeter进行并发测试。由于不是此处的重点,不再做演示,感兴趣的小伙伴可以留言,我会整理下相关的教程。

后续

这篇文章通过数据库乐观锁已经解决了库存超卖的问题,不过效率上并不是最优方案,后续会完善其他方案的演示。文中如有错漏之处,还望大家不吝赐教。



推荐阅读
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 本文深入解析了WCF Binding模型中的绑定元素,详细介绍了信道、信道管理器、信道监听器和信道工厂的概念与作用。从对象创建的角度来看,信道管理器负责信道的生成。具体而言,客户端的信道通过信道工厂进行实例化,而服务端则通过信道监听器来接收请求。文章还探讨了这些组件之间的交互机制及其在WCF通信中的重要性。 ... [详细]
  • 本文详细介绍了如何安全地手动卸载Exchange Server 2003,以确保系统的稳定性和数据的完整性。根据微软官方支持文档(https://support.microsoft.com/kb833396/zh-cn),在进行卸载操作前,需要特别注意备份重要数据,并遵循一系列严格的步骤,以避免对现有网络环境造成不利影响。此外,文章还提供了详细的故障排除指南,帮助管理员在遇到问题时能够迅速解决,确保整个卸载过程顺利进行。 ... [详细]
  • 【问题】在Android开发中,当为EditText添加TextWatcher并实现onTextChanged方法时,会遇到一个问题:即使只对EditText进行一次修改(例如使用删除键删除一个字符),该方法也会被频繁触发。这不仅影响性能,还可能导致逻辑错误。本文将探讨这一问题的原因,并提供有效的解决方案,包括使用Handler或计时器来限制方法的调用频率,以及通过自定义TextWatcher来优化事件处理,从而提高应用的稳定性和用户体验。 ... [详细]
  • 在编译 PHP7 的 PDO MySQL 扩展时,可能会遇到 `[mysql_driver.lo]` 错误 1。该问题通常出现在 `pdo_mysql_fetch_error_func` 函数中。本文详细介绍了导致这一错误的常见原因,包括依赖库版本不匹配、编译选项设置不当等,并提供了具体的解决步骤和调试方法,帮助开发者快速定位并解决问题。 ... [详细]
  • 基于JSP和SSM框架的超市收银系统毕业设计论文及源代码分析
    本研究基于JSP和SSM框架开发了一套超市收银系统,旨在提升超市收银效率和管理便捷性。系统运行环境包括JDK 1.8、Tomcat 7.0、MySQL数据库以及HBuilderX(也可使用WebStorm)作为前端开发工具,后端开发则采用Eclipse(IntelliJ IDEA亦可)。该系统实现了商品管理、订单处理、库存管理和报表生成等核心功能,具有良好的稳定性和扩展性。通过详细的设计与实现过程,本文为相关领域的研究提供了有价值的参考。 ... [详细]
  • QT框架中事件循环机制及事件分发类详解
    在QT框架中,QCoreApplication类作为事件循环的核心组件,为应用程序提供了基础的事件处理机制。该类继承自QObject,负责管理和调度各种事件,确保程序能够响应用户操作和其他系统事件。通过事件循环,QCoreApplication实现了高效的事件分发和处理,使得应用程序能够保持流畅的运行状态。此外,QCoreApplication还提供了多种方法和信号槽机制,方便开发者进行事件的定制和扩展。 ... [详细]
  • 短信验证码安全性堪忧,多因素认证或成未来主流
    短信验证码安全性堪忧,多因素认证或成未来主流 ... [详细]
  • 在Android开发中,BroadcastReceiver(广播接收器)是一个重要的组件,广泛应用于多种场景。本文将深入解析BroadcastReceiver的工作原理、应用场景及其具体实现方法,帮助开发者更好地理解和使用这一组件。通过实例分析,文章详细探讨了静态广播的注册方式、生命周期管理以及常见问题的解决策略,为开发者提供全面的技术指导。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
  • 寒假作业解析:第三周 2月12日 第7题
    尽快完成之前的练习任务!每日一练2.1 Problem A Laurenty and Shop 的题目要求是选择两条不同的路线以最小化总的等待时间。简要分析:通过对比不同路线的等待时间,可以找到最优解。此问题可以通过动态规划或贪心算法来解决,具体取决于路线的复杂性和约束条件。 ... [详细]
  • 第二章:Kafka基础入门与核心概念解析
    本章节主要介绍了Kafka的基本概念及其核心特性。Kafka是一种分布式消息发布和订阅系统,以其卓越的性能和高吞吐量而著称。最初,Kafka被设计用于LinkedIn的活动流和运营数据处理,旨在高效地管理和传输大规模的数据流。这些数据主要包括用户活动记录、系统日志和其他实时信息。通过深入解析Kafka的设计原理和应用场景,读者将能够更好地理解其在现代大数据架构中的重要地位。 ... [详细]
  • 在Python网络编程中,多线程技术的应用与优化是提升系统性能的关键。线程作为操作系统调度的基本单位,其主要功能是在进程内共享内存空间和资源,实现并行处理任务。当一个进程启动时,操作系统会为其分配内存空间,加载必要的资源和数据,并调度CPU进行执行。每个进程都拥有独立的地址空间,而线程则在此基础上进一步细化了任务的并行处理能力。通过合理设计和优化多线程程序,可以显著提高网络应用的响应速度和处理效率。 ... [详细]
author-avatar
木马
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有