高併發二(秒殺系統-—樂觀鎖、悲觀鎖、Synchronized)

一、情景

某件商品(庫存只有100件),如何防止在秒殺活動中被超賣的問題(500的併發)

二、不加鎖

   @ApiOperation("下單")
    @RequestMapping(value = "/createWrongOrder/{stockId}", method = RequestMethod.GET)
    @ResponseBody
    public String createWrongOrder(@PathVariable String stockId) {
        return orderService.createWrongOrder(stockId);
    }
 /**
     * 庫存100,併發500 下同
     * 結果:不正常
     * 庫存扣50,下單500
     */
    @Override
    @Transactional
    public String createWrongOrder(String stockId) {
        //校驗庫存
        Stock stock = checkStock(stockId);
        //扣庫存
        saleStock(stock);
        //創建訂單
        return createOrder(stock);
    }
  private Stock checkStock(String stockId) {
        Stock stock = stockDao.findById(stockId).orElseThrow(() -> new NotFoundException("商品不存在"));
        if (stock.getSale() >= stock.getCount()) {
            throw new BadRequestException("庫存不足");
        }
        return stock;
    }

   private void saleStock(Stock stock) {
        stock.setSale(stock.getSale() + 1);
        stock.setUpdateTime(new Date());
        stockDao.save(stock);
    }

   private String createOrder(Stock stock) {
        StockOrder order = new StockOrder();
        order.setStockId(stock.getStockId());
        order.setName(stock.getName());
        order.setCreateTime(new Date());
        order.setUpdateTime(new Date());
        StockOrder stockOrder = stockOrderDao.save(order);
        return "ID:" + stockOrder.getStockOrderId() + " == Name:" + stockOrder.getName();
    }

三、樂觀鎖

   @ApiOperation("下單-樂觀加鎖")
    @RequestMapping(value = "/createOptimisticOrder/{stockId}", method = RequestMethod.GET)
    @ResponseBody
    public String createOptimisticOrder(@PathVariable String stockId) {
        return orderService.createOptimisticOrder(stockId);
    }
    /**
     * 庫存100,併發500 下同
     * 結果:正常
     * 庫存扣51,下單51
     */
    @Override
    @Transactional
    public String createOptimisticOrder(String stockId) {
        //校驗庫存
        Stock stock = checkStock(stockId);
        //樂觀鎖釦庫存
        saleStockOptimistic(stock);
        //創建訂單
        return createOrder(stock);
    }
    private void saleStockOptimistic(Stock stock) {
        int count = stockDao.updateByOptimistic(stock.getSale() + 1, stock.getStockId(), stock.getVersion());
        if (count == 0) {
            throw new BadRequestException("併發更新庫存失敗,version不匹配");
        }
    }

    @Modifying
    @Query("update Stock set sale = ?1 ,version = version+1 where stockId = ?2 and version = ?3")
    int updateByOptimistic(Integer sale, String stockId, Integer version);

四、悲觀鎖

  @ApiOperation("下單-悲觀加鎖")
    @RequestMapping(value = "/createPessimisticOrder/{stockId}", method = RequestMethod.GET)
    @ResponseBody
    public String createPessimisticOrder(@PathVariable String stockId) {
        return orderService.createPessimisticOrder(stockId);
    }
 /**
     * 庫存100,併發500 下同
     * 結果:正常
     * 庫存扣100,下單100
     */
    @Override
    @Transactional
    public String createPessimisticOrder(String stockId) {
        //悲觀鎖校驗庫存
        Stock stock = checkPessimisticStock(stockId);
        //扣庫存
        saleStock(stock);
        //創建訂單
        return createOrder(stock);
    }
 private Stock checkPessimisticStock(String stockId) {
        Stock stock = stockDao.findByStockIdAndPessimistic(stockId);
        if (stock.getSale() >= stock.getCount()) {
            throw new BadRequestException("庫存不足");
        }
        return stock;
    }

   @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT stock FROM Stock stock WHERE stock.stockId= ?1")
    Stock findByStockIdAndPessimistic(String stockId);

五、Sync加鎖失效

  @ApiOperation("下單-Sync加鎖失效")
    @RequestMapping(value = "/createSynchronizedOrder/{stockId}", method = RequestMethod.GET)
    @ResponseBody
    public String createSynchronizedOrder(@PathVariable String stockId) {
        return orderService.createSynchronizedOrder(stockId);
    }
 /**
     * 庫存100,併發500 下同
     * 結果:不正常
     * 庫存扣51,下單500
     */
    /**
     * 由於Spring事務是通過AOP實現的,所以在 createSynchronizedOrder 方法執行之前會有開啓事務,之後會有提交事務邏輯。
     * 而synchronized代碼塊執行是在事務之內執行的,可以推斷在synchronized代碼塊執行完時,事務還未提交,其他
     * 線程進入synchronized代碼塊後,讀取的庫存數據不是最新的。
     *
     * @param stockId
     * @return
     */
    @Override
    @Transactional
    public synchronized String createSynchronizedOrder(String stockId) {
        //校驗庫存
        Stock stock = checkStock(stockId);
        //扣庫存
        saleStock(stock);
        //創建訂單
        return createOrder(stock);
    }

六、Sync加鎖成功

  @ApiOperation("下單-Sync加鎖成功")
    @RequestMapping(value = "/createSynchronizedOrderV2/{stockId}", method = RequestMethod.GET)
    @ResponseBody
    public String createSynchronizedOrderV2(@PathVariable String stockId) {
        return orderService.createSynchronizedOrderV2(stockId);
    }
 /**
     * 庫存100,併發500 下同
     * 結果:正常
     * 庫存扣100,下單100
     */
    @Override
    public synchronized String createSynchronizedOrderV2(String stockId) {
        return (SpringUtil.getBean(this.getClass())).createSynchronizedOrder(stockId);
    }

七、源碼

https://github.com/akeung/akeung_learning.git

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章