一、情景
某件商品(庫存只有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