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

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(使用前将#替换为@)

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

随机推荐

  • 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 如有好文章投稿 请点击 这里了解详情 功能需求 设计一个秒杀系统 初始方案 商品表设计 热销商品提供给用户秒杀 有初始库存