面试实战考核:设计一个高并发下的下单功能(代码片段)

author author     2022-12-25     609

关键词:

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

初始方案

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

@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 "购买失败,库存不足";
    

在看看运行情况

技术图片

订单表:

技术图片

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

共同进步,学习分享

欢迎大家关注我的公众号【风平浪静如码】,海量Java相关文章,学习资料都会在里面更新,整理的资料也会放在里面。

觉得写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!

海量面试、架构资料分享

技术图片

高并发下秒杀商品,这9个细节得知道(代码片段)

...了。前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出现在商城的促销活动中,指定了一定... 查看详情

高并发下秒杀商品,你必须知道的9个细节(代码片段)

...了。前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出现在商城的促销活动中,指定了一定... 查看详情

秒杀系统架构分析与实战(代码片段)

1秒杀业务分析正常电子商务流程秒杀业务的特性2秒杀技术挑战1、对现有网站业务造成冲击2、高并发下的应用、数据库负载3、突然增加的网络及服务器带宽4、直接下单5.如何控制秒杀商品页面购买按钮的点亮6.如何只允许第一... 查看详情

php和redis实现在高并发下的抢购及秒杀功能示例详解(代码片段)

抢购、秒杀是平常很常见的场景,面试的时候面试官也经常会问到,比如问你淘宝中的抢购秒杀是怎么实现的等等。抢购、秒杀实现很简单,但是有些问题需要解决,主要针对两个问题:一、高并发对数据库产生的压力二、竞争... 查看详情

高并发下的web异步处理方案(代码片段)

...理与web容器接收线程为同一线程,每一次Http请求都由一个线程从头到尾负责处理。​如果一个请求业务处理涉及IO操作,比如访问数据库、调用第三方服务 查看详情

day783.网络通信优化之i/o模型:如何解决高并发下i/o瓶颈-java性能调优实战(代码片段)

网络通信优化之I/O模型:如何解决高并发下I/O瓶颈Hi,我是阿昌,今天学习记录的是关于网络通信优化之I/O模型:如何解决高并发下I/O瓶颈。提到JavaI/O,相信你一定不陌生。可能使用I/O操作读写文件,也可... 查看详情

高并发下乐观锁实现(代码片段)

...户账户表,可考虑利用数据库乐观锁的办法解决。1、表设计   需要在表中新增version字段,可定义为bigint类型,初始值可设置为02、更新语句mybatis的实现<updateid="updateConsumeStarWithLook">updatesys_user_consume_starsetcurrent_con... 查看详情

高并发下如何保证数据库和缓存双写一致性?(代码片段)

...题变得更加严重。我很负责的告诉你,该问题无论在面试,还是工作中遇到的概率非常大,所以非常有必要跟大家一起探讨一下。今天这篇文章我会从浅入深,跟大家一起聊聊&# 查看详情

高并发下如何避免产生重复数据?(代码片段)

前言最近测试给我提了一个bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。追查原因之后发现,这个事情没想象中简单,可以说一波多折。1.需求产品有个需求:用户选择一些品牌࿰... 查看详情

高并发下如何避免产生重复数据?(代码片段)

前言最近测试给我提了一个bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。追查原因之后发现,这个事情没想象中简单,可以说一波多折。1.需求产品有个需求:用户选择一些品牌࿰... 查看详情

redis实现高并发下的抢购/秒杀功能

...lhttps://www.cnblogs.com/TankXiao/p/4045439.html之前写过一篇文章,高并发的解决思路(点此进入查看),今天再次抽空整理下实际场景中的具体代码逻辑实现吧:抢购/秒杀 查看详情

漫画:高并发下的hashmap(代码片段)

这一期我们来讲解高并发环境下,HashMap可能出现的致命问题。               HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突... 查看详情

高并发下service层的写法(代码片段)

最近在项目里遇到一个坑,先上简易版的描述:每次从库里查询一下库存余量,每次购买一个商品。数据库:store为库存量。service层代码:@Overridepublicsynchronizedvoidsell()System.out.println("<======"+System.currentTimeMillis());//根据局id获取... 查看详情

数据存储redis第四章:高并发下实现分布式锁(代码片段)

...tributedLock()StringlockKey="distributedLockKey";//给每个线程都设置一个唯一标识,避免出现程序执行的时间超过设置的过期时间,导致其他线程删除了自己的锁 查看详情

数据存储redis第四章:高并发下实现分布式锁(代码片段)

...tributedLock()StringlockKey="distributedLockKey";//给每个线程都设置一个唯一标识,避免出现程序执行的时间超过设置的过期时间,导致其他线程删除了自己的锁 查看详情

redis实现高并发下的抢购秒杀功能

...到了抢购问题!现在分享下。抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1高并发对数据库产生的压力2竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存来... 查看详情

httpclient高并发下性能优化-http连接池(代码片段)

...受阻2.http连接池只适用于请求是经常访问同一主机(或同一个接口)的情况下3.并发数不高的情况下资源利用率低下那么,当你的业务符合上面3点,那么你可以考虑使用http连接池来提高服务器性能使用http连接池的优点:1.复用http连接,... 查看详情

高并发下,hashmap会产生哪些问题?(代码片段)

HashMap在高并发环境下会产生的问题HashMap其实并不是线程安全的,在高并发的情况下,会产生并发引起的问题:比如:HashMap死循环,造成CPU100%负载触发fail-fast下面逐个分析下出现上述情况的原因:HashMap死循环的原因HashMap进行存... 查看详情