【黑马点评】优惠券秒杀(单体模式)

news2024/9/24 9:21:43

优惠券秒杀业务流程

一人一单:要保证一个用户最多只能下一单

在这里插入图片描述

  1. 用户提交优惠券id
  2. 根据优惠券id查询优惠券信息
  3. 判断秒杀是否开始或结束,如果秒杀尚未开始或已经结束就直接返回错误信息
  4. 判断库存是否充足,如果库存数量小于1就直接返回错误信息
  5. 根据用户id和订单id查询订单,判断该用户是否下过单,如果下过单就直接返回错误信息
  6. 扣减库存
  7. 创建购买优惠券的订单

线程安全问题

存在两个典型的多线程安全问题:

  1. 先判断库存是否充足,再扣减库存 => 库存超卖问题
  2. 先判断用户是否下过单,再创建订单 => 一人一单线程安全问题

解决线程安全问题的方法:加锁

通常由两种解决方案:乐观锁&悲观锁
在这里插入图片描述在这里插入图片描述

其中,乐观锁需要在更新数据时判断数据是否被其他线程修改,可以通过比较现在查到的数据与之前查到的数据的数据值或版本号是否一致来实现。

乐观锁适合更新数据的场景(本来就有数据),如果是插入数据的场景(本来没有数据)就只能使用悲观锁来解决。

库存超卖

对于库存超卖问题,属于更新数据的场景,本项目中使用乐观锁解决,更新数据时判断数据值是否发生改变。

具体的做法:在扣减库存的mybatis-plus链式查询语句中追加eq("stock", voucher.getStock())条件,其中voucher是前面的步骤中查到的优惠券对象,调用voucher.getStock()方法获得之前查到的库存数据,而stock字段的值就是现在的数据。
在这里插入图片描述对应的sql语句:

update tb_seckill_voucher 
set stock = stock - 1
where voucher_id = {voucherId}
  and stock = {voucher.getStock()} 

用Apache的JMeter做压力测试:库存数量100,线程数200。
理论上来说,异常率(失败的请求占总请求数量的百分比)应该在50%左右,但实际的结果是异常率高达90%。
在这里插入图片描述
经过分析发现异常率高的原因在于:由于我们设置的条件是只有之前的数据和当前数据完全一致时才能成功扣减库存,因此如果有多个线程在查询库存数量时查询到同一个数量值,那么这些线程中只有一个能执行成功。其他线程都会判断得出数据被修改,然后直接结束流程返回错误信息。但是实际上此时只要还有库存,这些线程也可以执行扣减库存的操作。

于是做出一下改进:将stock = {voucher.getStock()} 的条件改为stock > 0
在这里插入图片描述此时再进行压测,发现异常率刚好是50%
在这里插入图片描述

一人一单线程安全问题

初始方案

而对于一人一单问题,属于插入数据的场景,只能使用悲观锁解决。

最初始的方案是将 “5.判断是否下过单;6. 扣减库存;7. 创建订单” 这三个步骤提取出来,创建一个新的方法createVoucherOrder,在原本的seckillVoucher方法中调用。
注意:

  1. 需要将这个方法设置为sychronized方法。
  2. 为了保证6、7两个操作的原子性,需要为该方法加上@Transactional注解,此时原本的seckillVoucher方法就不需要加@Transactional注解了。

在这里插入图片描述

测试后发现,确实可以解决一人一单问题,但由于锁的粒度太大,执行效率较低

sychronized方法是对方法所在类的Class对象加锁,在这里就是对VoucherOrderServiceImpl对象加锁,而这个对象全局就只有一个。原本我们只是想控制同一用户的多个并发请求之间的线程安全问题,现在所有用户执行该方法创建订单时都得改为串行执行,因此执行效率较低。

改进1

改进方法就是:不使用同步方法,而是使用同步代码块(同步代码块将囊括该方法中的包括最后一个return语句在内的全部代码),并且将加锁对象设置为userId.toString().intern(),即在字符串常量池中创建一个内容为用户id的字符串对象,该对象对于每一个用户是唯一的。

在这里插入图片描述

改进2

做了这一改进之后,仍存在一些问题。

由于我们加了@Transactional注解,所以createVoucherOrder方法受Spring事务的控制,由Spring负责在整个方法执行完之后提交事务。而Spring的事务是基于AOP实现的,AOP是基于代理实现的,代理对象会先调用目标对象的createVoucherOrder方法,执行完以后再执行提交事务的代码。

当同步代码块结束时,即createVoucherOrder方法运行完最后一行时,锁就释放了,此时事务尚未提交,数据尚未同步到数据库。如果在这时其他线程插入进来拿到锁进入同步代码块执行,查到的就是脏数据,会造成线程安全问题。

解决方法就是:

  1. 对整个createVoucherOrder方法加锁,即在原本的seckillVoucherOrder方法中,对调用createVoucherOrder的语句加锁。
  2. 并且要通过代理对象调用createVoucherOrder方法,这样事务才能生效。
    • 要获得事务代理对象需要用到AspectJ,并且要在启动类中添加@EnableAspectJAutoProxy(exposeProxy = true)注解。

在这里插入图片描述

总结

  1. 基于乐观锁解决库存超卖问题
    • 在更新数据时判断当前查到的库存值是否充足,如果库存不足就直接返回错误信息。
    • 判断条件从“当前库存值与之前查到的库存值是否一致”弱化为“当前查到的库存值是否充足”,依然可以解决库存超卖问题,并且可以降低异常率。
  2. 基于悲观锁解决一人一单线程安全问题
    • 将从“判断是否下过单”开始的代码提取出来,作为子方法
    • 该子方法中包括了扣减库存和创建订单两个操作,为保证原子性,需要为子方法加上@Transactional注解
    • 为了解决一人一单线程安全问题,需要对原方法中调用子方法的语句加上sychronized锁(悲观锁),加锁对象为userId.toString().intern()
      • 之所以使用同步代码块的方式,而不是将子方法设为sychronized方法,是因为sychronized方法是对全局唯一的VoucherServiceOrderImpl对象加锁,锁的粒度太大,执行效率较低。
      • 加锁对象是在字符串常量池中创建的内容为用户id的字符串对象,该对象对于每一个用户是唯一的。这里的intern()必不可少,否则每一次调用userId.toString()都会创建一个新的字符串对象,这样每一个线程都使用不同的锁,相当于根本没加锁。
      • 为了保证提交完事务之后再释放锁,所以在原方法中调用子方法的语句上加锁,而不是在子方法内部加锁。并且为了保证事务生效,调用子方法时通过事务代理对象来调用,而不是this。这样加锁的内容实际上是包含了事务代理对象中关于事务处理的增强代码的。

上述方案在单体模式(只有一台Tomcat服务器)下没有问题,但是在使用Tomcat集群的分布式系统中无法正确解决一人一单线程安全问题。原因在于:上述方案中使用的锁userId.toString().intern()是放在字符串常量池中的,如果只有一台Tomcat服务器 => 只有一个JVM => 只有一个字符串常量池 => 每个用户只有一把锁,但如果有多台Tomcat服务器 ,就无法保证每个用户只有一把锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2078199.html

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

相关文章

火源类型检测系统源码分享 # [一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]

火源类型检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

word技巧:保护Word文档页眉,确保内容不被随意修改

我们设置了页眉内容之后,不想其他人修改自己的页眉内容,我们可以设置加密的,设置方法如下: 先将页眉设置好,退出页眉设置之后,我们选择布局功能,点击分隔符 – 连续 设置完之后页面分为上下两节…

无线领夹麦克风哪个牌子好?麦克风品牌排行榜前十名推荐

在直播与Vlog盛行的当下,一款性能卓越、稳定可靠的无线领夹麦克风无疑是内容创作者的最佳拍档。然而,市场上的无线领夹麦克风种类繁多,质量参差不齐。不少商家为追求利润最大化,采用廉价材料制造,导致收音效果差、噪音…

SqlHelper 使用EF-Core框架

public async Task AddAsync<T>(T entity) where T : class{await _context.Set<T>().AddAsync(entity);await _context.SaveChangesAsync();} //增加实体 public 表面方法是公开的&#xff0c;所有其他类都可以调用 async 表方法内可能包含异步操作&#xff0c…

快手如何去水印教程,推荐五款免费高效去水印工具

随着互联网的快速发展&#xff0c;视频内容创作和分享变得越来越普及。很多视频创作者在分享自己的作品时&#xff0c;都希望能够去除视频中的水印&#xff0c;以保护自己的版权或提高视频的观感。本文将为您介绍五款高效的视频去水印工具&#xff0c;帮助您轻松去除视频中的水…

特斯拉CEO马斯克呼吁加州加强AI监管,市场关注国际贸易动态

特斯拉与AI监管&#xff1a;马斯克的倡议 特斯拉&#xff08;TSLA&#xff09;首席执行官埃隆马斯克&#xff08;Elon Musk&#xff09;周一在社交媒体上再次表达了他对人工智能&#xff08;AI&#xff09;监管的坚定立场。他呼吁加州通过SB 1047法案&#xff0c;要求科技公司和…

前端自我提升秘籍:用这个工具快速成为全栈开发者!

最近&#xff0c;有不少前端开发者都在讨论如何进一步提升自己的技能&#xff0c;尤其是那些一直在公司里负责后台管理系统的同学。毕竟&#xff0c;后台管理系统虽然重要&#xff0c;但往往功能固定、需求清晰&#xff0c;相对来说技术挑战有限。要想突破自己的技术瓶颈&#…

黑神话悟空配置要求_2024年黑神话悟空游戏电脑配置推荐

《黑神话&#xff1a;悟空》作为一款采用虚幻引擎打造的高品质游戏&#xff0c;对硬件配置有着一定的要求。包括操作系统、处理器、内存、显卡等多方面需求。有些同学一直问&#xff0c;黑神话悟空配置要求是什么&#xff1f;下面小编就针对这个问题给大家介绍黑神话悟空官方配…

查找算法刷题【二分查找算法】

一、原理 如下图所示的就是二分查找算法的原理&#xff1a; 注意&#xff1a;二分查找算法中一个重要的思想&#xff1a;数组和函数是一样的概念&#xff0c;对可以使用二分法查找要求如下所示。 &#xff08;1&#xff09;数组f[i]是有序数组 &#xff08;2&#xff…

mapstruct和lombok同时使用时,转换实体类时数据丢失

全局搜一下maps&#xff0c;找到你进行转换的方法 可以看到新建了TswCaseInfoPlus后直接返回了&#xff0c;说明TswCaseInfoPlus没有set方法&#xff0c;或者说编译后lombok没生效 在pom文件中&#xff0c;编译打包插件中将lombok&#xff0c;mapstruct&#xff0c;lombok-map…

武汉流星汇聚:未来已来,亚马逊多元化战略引领电商行业新纪元

在全球电商的浩瀚星空中&#xff0c;亚马逊无疑是最耀眼的明星之一。凭借其强大的品牌影响力、完善的运营体系以及不断创新的技术应用&#xff0c;亚马逊不仅在美国市场稳坐头把交椅&#xff0c;更在全球范围内不断拓展其业务版图。展望未来&#xff0c;亚马逊的未来发展前景依…

新生开学,需要带的东西有哪些!

每年开学季&#xff0c;先给学子们来个灵魂拷问&#xff1a;你们是否已经做好了迎接新挑战的准备&#xff0c;还是在迷茫中踏入新的征程&#xff1f; 不管怎样&#xff0c;我们先来解决下当下寄件问题。上大学要带啥&#xff1f;行李怎么寄便宜&#xff1f; 寄快递注意事项 …

[论文笔记] LLM-ICL论文:AI模型对prompt格式分隔符的敏感性——结构化Prompt格式

又见惊雷&#xff0c;结构化Prompt格式小小变化竟能让LLM性能波动高达76%&#xff0c;ICLR2024

在Windows上用Visual Studio编译Tesseract

Tesseract是著名的OCR&#xff08;文字识别&#xff09;开源项目。我想自己编译它的源代码。然而总体而言&#xff0c;大型开源项目在Windows上编译多少都会有些磕磕绊绊&#xff0c;如果有幸最后成功了&#xff0c;都值得写一篇文章来纪念一下。这便是本文的由来。 编译环境&…

CRMEB 多店版供应商入驻及管理

一、功能介绍 供应商于移动端提出入驻申请&#xff0c;平台可于入驻申请中审核&#xff0c;审核通过后&#xff0c;该供应商可于平台上架商品。 二、供应商申请 供应商入驻申请 一、功能介绍 平台可以设置商品的供应商&#xff0c;用户下单后由供应商直接发货&#xff0c;自…

FT8493PA-RT隔离型SOP8快充充电器电源IC

FT8493XX&#xff0c;FT8493PA-RT是应用于隔离反激电压变换器的- -款高性能PWM控制器.芯片工作在CCM模式,工作频率随着负载降低而减小.拥有更高的效率,更快的瞬态响应以及更低的待机功耗.同时使用内部抖频技术可以更好的处理ENI. FT8493XX内部集成高压启动可以更快的起机以及更…

智能化Web3:如何利用人工智能优化区块链技术

随着数字技术的飞速发展&#xff0c;Web3和人工智能&#xff08;AI&#xff09;逐渐成为技术创新的两个重要领域。Web3代表了去中心化互联网的未来&#xff0c;而AI则是驱动智能化应用的核心技术。当AI与Web3相结合时&#xff0c;将会对区块链技术的优化产生深远的影响。本文将…

Elastic Stack--介绍及架构部署:ElasticSearch、Kibana、Filebeat的RPM包部署安装及基础使用

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 学习B站博主教程笔记&#xff1a; 最新版适合自学的ElasticStack全套视频&#xff08;Elk零基础入门到精通教程&#xff09;Linux运维必备—Elastic…

HTML学习笔记——用HTML记录学习过程5-全局属性

全局属性可以用来配置元素的共有行为 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>5-全局属性</title> </head> <body><div>id属性用于指定html元素的唯一id</div>…

Linux常用应急排查命令(持续更新)20240827更

1、history &#xff08;1&#xff09;使用history查看历史命令&#xff0c;分析攻击者使用过何命令 history&#xff08;2&#xff09;但攻击者也能回使用history -c 清除掉历史命令 history -c&#xff08;3&#xff09;使用cat查看cat /root/.bash_history 文件也可以查看…