秒杀系统设计,高并发下的下单功能设计

2023-11-15

来源:常大皮卡丘,

blog.csdn.net/u013815546/article/details/53928912

如有好文章投稿,请点击 → 这里了解详情


功能需求:设计一个秒杀系统


初始方案


商品表设计:热销商品提供给用户秒杀,有初始库存。


@Entity

public class SecKillGoods implements Serializable{

    @Id

    private String id;

 

    /**

     * 剩余库存

     */

    private Integer remainNum;

 

    /**

     * 秒杀商品名称

     */

    private String goodsName;

}


秒杀订单表设计:记录秒杀成功的订单情况


@Entity

public class SecKillOrder implements Serializable {

    @Id

    @GenericGenerator(name = "PKUUID", strategy = "uuid2")

    @GeneratedValue(generator = "PKUUID")

    @Column(length = 36)

    private String id;

 

    //用户名称

    private String consumer;

 

    //秒杀产品编号

    private String goodsId;

 

    //购买数量

    private Integer num;

}


Dao设计:主要就是一个减少库存方法,其他CRUD使用JPA自带的方法


public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{

 

    @Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1")

    @Modifying(clearAutomatically = true)

    @Transactional

    int reduceStock(String id,Integer remainNum);

}


数据初始化以及提供保存订单的操作:


@Service

public class SecKillService {

 

    @Autowired

    SecKillGoodsDao secKillGoodsDao;

 

    @Autowired

    SecKillOrderDao secKillOrderDao;

 

    /**

     * 程序启动时:

     * 初始化秒杀商品,清空订单数据

     */

    @PostConstruct

    public void initSecKillEntity(){

        secKillGoodsDao.deleteAll();

        secKillOrderDao.deleteAll();

        SecKillGoods secKillGoods = new SecKillGoods();

        secKillGoods.setId("123456");

        secKillGoods.setGoodsName("秒杀产品");

        secKillGoods.setRemainNum(10);

        secKillGoodsDao.save(secKillGoods);

    }

 

    /**

     * 购买成功,保存订单

     * @param consumer

     * @param goodsId

     * @param num

     */

    public void generateOrder(String consumer, String goodsId, Integer num) {

        secKillOrderDao.save(new SecKillOrder(consumer,goodsId,num));

    }

}


下面就是controller层的设计


@Controller

public class SecKillController {

 

    @Autowired

    SecKillGoodsDao secKillGoodsDao;

    @Autowired

    SecKillService secKillService;

 

    /**

     * 普通写法

     * @param consumer

     * @param goodsId

     * @return

     */

    @RequestMapping("/seckill.html")

    @ResponseBody

    public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {

        //查找出用户要买的商品

        SecKillGoods goods = secKillGoodsDao.findOne(goodsId);

        //如果有这么多库存

        if(goods.getRemainNum()>=num){

            //模拟网络延时

            Thread.sleep(1000);

            //先减去库存

            secKillGoodsDao.reduceStock(num);

            //保存订单

            secKillService.generateOrder(consumer,goodsId,num);

            return "购买成功";

        }

        return "购买失败,库存不足";

    }

}


上面是全部的基础准备,下面使用一个单元测试方法,模拟高并发下,很多人来购买同一个热门商品的情况。


@Controller

public class SecKillSimulationOpController {

 

    final String takeOrderUrl = "http://127.0.0.1:8080/seckill.html";

 

    /**

     * 模拟并发下单

     */

    @RequestMapping("/simulationCocurrentTakeOrder")

    @ResponseBody

    public String simulationCocurrentTakeOrder() {

        //httpClient工厂

        final SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();

        //开50个线程模拟并发秒杀下单

        for (int i = 0; i < 50; i++) {

            //购买人姓名

            final String consumerName = "consumer" + i;

            new Thread(new Runnable() {

                @Override

                public void run() {

                    ClientHttpRequest request = null;

                    try {

                        URI uri = new URI(takeOrderUrl + "?consumer=consumer" + consumerName + "&goodsId=123456&num=1");

                        request = httpRequestFactory.createRequest(uri, HttpMethod.POST);

                        InputStream body = request.execute().getBody();

                        BufferedReader br = new BufferedReader(new InputStreamReader(body));

                        String line = "";

                        String result = "";

                        while ((line = br.readLine()) != null) {

                            result += line;//获得页面内容或返回内容

                        }

                        System.out.println(consumerName+":"+result);

                    } catch (Exception e) {

                        e.printStackTrace();

                    }

                }

            }).start();

        }

        return "simulationCocurrentTakeOrder";

    }

}


访问localhost:8080/simulationCocurrentTakeOrder,就可以测试了

预期情况:因为我们只对秒杀商品(123456)初始化了10件,理想情况当然是库存减少到0,订单表也只有10条记录。


实际情况:订单表记录





商品表记录




下面分析一下为啥会出现超库存的情况:


因为多个请求访问,仅仅是使用dao查询了一次数据库有没有库存,但是比较恶劣的情况是很多人都查到了有库存,这个时候因为程序处理的延迟,没有及时的减少库存,那就出现了脏读。如何在设计上避免呢?最笨的方法是对SecKillController的seckill方法做同步,每次只有一个人能下单。但是太影响性能了,下单变成了同步操作。


@RequestMapping("/seckill.html")

@ResponseBody

public synchronized String SecKill


改进方案


根据多线程编程的规范,提倡对共享资源加锁,在最有可能出现并发争抢的情况下加同步块的思想。应该同一时刻只有一个线程去减少库存。但是这里给出一个最好的方案,就是利用Oracle,MySQL的行级锁–同一时间只有一个线程能够操作同一行记录,对SecKillGoodsDao进行改造:


public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{

 

    @Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1 and g.remainNum>0")

    @Modifying(clearAutomatically = true)

    @Transactional

    int reduceStock(String id,Integer remainNum);

 

}


仅仅是加了一个and,却造成了很大的改变,返回int值代表的是影响的行数,对应到controller做出相应的判断。


@RequestMapping("/seckill.html")

    @ResponseBody

    public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {

        //查找出用户要买的商品

        SecKillGoods goods = secKillGoodsDao.findOne(goodsId);

        //如果有这么多库存

        if(goods.getRemainNum()>=num){

            //模拟网络延时

            Thread.sleep(1000);

            if(goods.getRemainNum()>0) {

                //先减去库存

                int i = secKillGoodsDao.reduceStock(goodsId, num);

                if(i!=0) {

                    //保存订单

                    secKillService.generateOrder(consumer, goodsId, num);

                    return "购买成功";

                }else{

                    return "购买失败,库存不足";

                }

            }else {

                return "购买失败,库存不足";

            }

        }

        return "购买失败,库存不足";

    }


在看看运行情况





订单表:





在高并发问题下的秒杀情况,即使存在网络延时,也得到了保障。



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

秒杀系统设计,高并发下的下单功能设计 的相关文章

  • 什么是内部类的合成反向引用

    我正在寻找应用程序中的内存泄漏 我正在使用的探查器告诉我寻找这些类型的引用 但我不知道我在寻找什么 有人可以解释一下吗 Thanks Elliott 您可以对 OUTER 类进行合成反向引用 但不能对内部类实例进行合成 e g class
  • getCurrentSession 在网络中休眠

    我正在使用 hibernate 和 jsp servlet 编写一个基于 Web 的应用程序 我读过有关sessionFactory getCurrentSession and sessionFactory openSession方法 我知
  • 将类转换为 JSONObject

    我有好几堂这样的课 我想将类转换为 JSONObject 格式 import java io Serializable import com google gson annotations SerializedName public cla
  • 每个搜索词显示一行,如果未找到则替换默认值

    Query SELECT product id name FROM product WHERE barcode in 681027 8901030349379 679046 679047 679082 679228 679230 67923
  • JAXB - 忽略元素

    有什么方法可以忽略 Jaxb 解析中的元素吗 我有一个很大的 XML 文件 如果我可以忽略其中一个大而复杂的元素 那么它的解析速度可能会快很多 如果它根本无法验证元素内容并解析文档的其余部分 即使该元素不正确 那就更好了 例如 这应该只生成
  • 在光标所在行强制关闭!

    嘿 我正在尝试创建一个应用程序来查找存储在 SQlite 数据库中的 GPS 数据 但我面临一个问题 我构建了一个 DbAdapter 类来创建数据库 现在我尝试使用以下函数从另一个类获取所有数据上的光标 public Cursor fet
  • 如何更改 Swagger-ui URL 前缀?

    我正在使用 Springfox Swagger2 和 Spring boot 1 5 9 我可以通过此链接访问 swagger UI http localhost 8090 swagger ui html http localhost 80
  • MySQL 将 ÅäÖ 视为 AAO?

    这两个查询给了我完全相同的结果 select from topics where name Harligt select from topics where name H rligt 这怎么可能 看起来mysql在搜索时会将 翻译成aao
  • 无需递归即可对可观察结果进行分页 - RxJava

    我有一个非常标准的 API 分页问题 您可以通过一些简单的递归来处理 这是一个捏造的例子 public Observable
  • UseCompressedOops JVM 标志有什么作用以及何时应该使用它?

    HotSpot JVM 标志是什么 XX UseCompressedOops我应该做什么以及什么时候使用它 在 64 位 Java 实例上使用它 与不使用它 时 我会看到什么样的性能和内存使用差异 去年大多数 HotSpot JVM 都默认
  • Java 8 Stream,获取头部和尾部

    Java 8 引入了Stream http download java net jdk8 docs api java util stream Stream html类似于 Scala 的类Stream http www scala lang
  • 如何向页面添加 HTML 页眉和页脚?

    如何使用 itext 从 html 源添加标题到 pdf 目前 我们已经扩展了 PdfPageEventHelper 并重写了这些方法 工作正常 但当我到达 2 个以上页面时 它会抛出 RuntimeWorkerException Over
  • 如何使用 Jersey 将嵌套列表封送为 JSON?我得到一个空数组或一个包含数组的单元素字典数组

    我正在开发一个使用 Jersey 将对象转换为 JSON 的项目 我希望能够写出嵌套列表 如下所示 data one two three a b c 我想要转换的对象首先将数据表示为 gt gt 我认为 Jersey 会做正确的事情 以上输
  • MySQL中如何存储小数?

    我尝试过将 DECIMAL 与 2 2 一起使用 但它不允许我使用它 我只想存储一个数字 例如 7 50 或 10 50 我需要将这两个数字保留在小数点后 但是当我刷新数据库时 它会将值重置为 0 99 有什么建议么 第一个参数DECIMA
  • 文本视图不显示全文

    我正在使用 TableLayout 和 TableRow 创建一个简单的布局 其中包含两个 TextView 这是代码的一部分
  • Azure Java SDK:ServiceException:ForbiddenError:

    尝试了基本位置检索器代码 如下所示 String uri https management core windows net String subscriptionId XXXXXXXX 5fad XXXXXX 9dfa XXXXXX St
  • 即使禁用安全性,OAuth 令牌 API 也无法在 Elastic Search 中工作

    我是 Elastic search 新手 使用 Elastic search 版本 7 7 1 我想通过以下方式生成 OAuth 令牌弹性搜索文档 https www elastic co guide en elasticsearch re
  • Spring Data Rest 多对多 POST

    首先 让我解释一下我的用例 这非常简单 有一个用户实体和一个服务实体 我使用 UserService 作为连接实体 连接表 在用户和服务之间建立多对多关联最初 会有一些用户集和一些服务集 用户可以在任何时间点订阅任何服务 在这种情况下 将向
  • 什么是 Java2D 处理程序线程?

    我创建了一个使用 Hibernate 的示例 java 应用程序 当我进行线程转储时 我观察到一个名为 Java2D Disposer 的奇怪线程 有人能告诉我该线程的功能吗 AWT 系统中的某些实体需要最终确定以释放资源 最突出的例子是j
  • 使用 DBCP 配置 Tomcat

    在闲置一段时间 几个小时 后 我们收到了 CommunicationsException 来自 DBCP 错误消息 在异常中 位于这个问题的末尾 但我没有看到任何配置文件中定义的 wait timeout 我们应该看哪里 在 tomcat

随机推荐

  • Python3,os模块还可以这样玩,自动删除磁盘文件,非必要切勿操作。

    删除磁盘下所有的文件 1 引言 2 代码实战 2 1 模块介绍 2 2 获取盘符 2 3 获取盘符下的目录 2 3 1 os listdir 2 3 2 os environ 2 3 3 os getenv 2 4 删除文件 2 4 1 删
  • ubuntu命令大全(建议收藏)

    Ubuntu 命令大全 一 文件目录类 1 建立目录 mkdir 目录名 2 删除空目录 rmdir 目录名 3 无条件删除子目录 rm rf 目录名 4 改变当前目录 cd 目录名 进入用户home目录 cd 进入上一级目录 cd 5 查
  • 《我想进大厂》之Redis夺命连环11问

    这是面试题系列第三篇 redis专题 说说Redis基本数据类型有哪些吧 字符串 redis没有直接使用C语言传统的字符串表示 而是自己实现的叫做简单动态字符串SDS的抽象类型 C语言的字符串不记录自身的长度信息 而SDS则保存了长度信息
  • 数字信号处理第四次试验:IIR数字滤波器设计及软件实现

    数字信号处理第四次试验 IIR数字滤波器设计及软件实现 前言 一 实验目的 二 实验原理与方法 三 实验环境 四 实验内容及步骤 五 实验结果截图 含分析 六 思考题 前言 为了帮助同学们完成痛苦的实验课程设计 本作者将其作出的实验结果及代
  • Yolov5部署成为Python接口 当然是用flask实现啦~ yolo5写成接口

    一 在此之前你是不是要先把yolov5跑通 yolov5的环境特别简单 建议在Ubuntu18 04下面部署 一次成型 省去很多麻烦 Ubuntu18 04 RTX2080 CUDA10 0 CUDNN7 4 1 Torch1 3 1GPU
  • IDEA中格式化代码快捷键

    一键格式化代碼 Ctrl Alt L 快捷键汇总链接 Intellij IDEA 快捷键整理 TonyCody Eclipse常用快捷键汇总 注意 如果按Ctrl Shift F在win10上会出现字体的简繁转换 再重复按键一次就可以转换回
  • 解决HTTP方式git push连接不上GitHub仓库等问题

    仅适用于HTTP方式PUSH 1 首先确定能正常访问GitHub 2 本地Git凭据要设置正确 使用命令设置本地Git凭据 git config global user name GitHub账户名 git config global us
  • 在eclipse环境下运行Servlet程序报错:404资源不可见或资源不愿意公开的解决办法

    在eclipse环境下运行Servlet程序报错 404资源不可见或资源不愿意公开 主要原因是缺少支持Servlet运行的API 具体原因如下 编译Servlet程序需要HttpServlet HttpServletRequest 和Htt
  • Git代码回滚提交分支

    在项目中会出现当前版本的代码仓出现问题 或者其它的一些情况 需要将当前的代码回滚到之前的某个指定版本上去 一 git reset回滚到指定的commit id版本 之后所提交的内容会被全部丢弃 如下图有4笔 commit 现在需要从当前的f
  • SpringCloud原生注解 @RefreshScope 的作用

    作用 SpringCloud 使用 RefreshScope注解 实现配置文件的动态加载 使用方法 修改配置文件后 不重启应用 在需要读取配置文件的地方添加 RefreshScope注解 发送POST请求 http localhost po
  • 数据结构之栈与队列

    目录 栈 后进先出 20 有效的括号 力扣 LeetCode 682 棒球比赛 力扣 LeetCode 队列 先进先出 26 删除有序数组中的重复项 力扣 LeetCode 27 移除元素 力扣 LeetCode 栈 后进先出 Stack
  • 北漂码农的真实心声:赚一线城市的钱,还二线城市的房贷

    作者 年素清 责编 王晓曼 出品 程序人生 ID coder life 我叫郭子洋 1993年出生在安徽南部的一座小城 父亲是某国企下属单位的一名车间工人 母亲四处打零工补贴家用 我还有一个比我大三岁的姐姐 虽然家境寒微 但父母非常注重我和
  • 信息学奥赛一本通 1180:分数线划定

    题目链接 http ybt ssoier cn 8088 problem show php pid 1180 include
  • 面向对象设计原则——接口隔离原则

    接口隔离原则 Interface Segregation Principle ISP 使用多个专门的接口 而不使用单一的总接口 即客户端不应该依赖那些它不需要的接口 根据接口隔离原则 当一个接口太大时 我们需要将它分割成一些更细小的接口 使
  • matplotlib画图:柱形图、堆叠柱形图、分组柱形图。

    matplotlib画图 柱形图 堆叠柱形图 分组柱形图 分组柱形图与堆叠柱形图的区别 堆叠柱形图 有助于帮助我们观察部分与整体之间的关系 如 2020年每个区域每个季度的销售情况 分组柱形图 当比较一个整体的某组成部分与其他整体对应组成部
  • maven的详细下载和安装

    一 maven的安装 1 首先去官网下载maven https maven apache org download cgi 直接下载就可以使用 如果想要下载历史版本 点击下方的archives可以了 2 下载完成后 解压下载的maven安装
  • Loughran&McDonald金融文本情感分析库

    今天看到一个预测股价的项目 其中用到pysentiment库对金融文本数据进行情感计算 查了下该库的官方文档 发现该库提供了两大情感分析 Harvard IV 4 英文通用情感分析 Loughran MCdonald 英文金融情感分析 py
  • [深度学习]C++调用Python-YOLO模型进行目标检测

    文章目录 前言 C 调用Python的步骤 修改YOLOv5源码 C 读取Python返回值 前言 目前深度学习算法大多数是基于Python实现 但一些项目的框架是用C 搭建 所以就出现了在C 中调用模型的问题 本文主要记录C 调用Pyth
  • Activity使用Dialog样式导致点击空白处自动关闭的问题

    将Activity设置成窗口的样式实现Dialog或者Popupwindow效果在开发中是很常用的一种方式 在AndroidMenifest xml中将需要设置的Activity增加android theme android style T
  • 秒杀系统设计,高并发下的下单功能设计

    来源 常大皮卡丘 blog csdn net u013815546 article details 53928912 如有好文章投稿 请点击 这里了解详情 功能需求 设计一个秒杀系统 初始方案 商品表设计 热销商品提供给用户秒杀 有初始库存