下單時需要判斷兩點:1.秒殺是否開始或結束2.庫存是否足夠
所以,我們的業務邏輯如下
1. 透過優惠券id取得優惠券資訊
2.判斷秒殺是否開始,如果未回傳錯誤訊息
3.判斷秒殺是否結束,如果已經結束返回錯誤訊息
4.如果在秒殺時間內,判斷庫存是否充足
5.如果充足,扣減庫存
6.創建訂單信息,並保存到優惠券訂單表中
6.1 保存訂單id
6.2保存用戶id
6.3保存優惠券id
7 .返回訂單id
程式碼實作:(Service層實作類別)
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服务实现类 * </p> * * @author 虎哥 * @since 2021-12-22 */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.获取优惠券信息 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判断是否已经开始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒杀尚未开始!"); } //3.判断是否已经结束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒杀已经结束了!"); } //4.判断库存是否充足 if (voucher.getStock() < 1) { Result.fail("库存不充足!"); } //5.扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId) .update(); if (!success){ Result.fail("库存不充足!"); } //6. 创建订单 VoucherOrder voucherOrder = new VoucherOrder(); //6.1添加订单id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //6.2添加用户id Long userId = UserHolder.getUser().getId(); voucherOrder.setUserId(userId); //6.3添加优惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //7.返回订单id return Result.ok(orderId); } }
我們先嘗試在高並發的情況下運行上述程式碼。 (使用jmx工具)
下圖是創建了兩百個線程,在一瞬間發出優惠券請求
但是我們看聚合報告,發現異常值只有45.5%,照道理來說應該是50%(因為庫存有100個,這裡發出了200個請求)
一看庫存數,好傢伙,是-9
訂單也是增加了109個,這顯然發生了超賣的問題。
那麼,為什麼會發生這種問題呢?
看圖說話:
按照我們正常的流程來走,就是線程1線查詢完庫存,然後扣減庫存,這個時候線程2再來查詢庫存,扣減庫存,這樣是沒問題的。
超賣的問題就出在,在訂單1查詢庫存後,發現是1,但還沒去扣減的時候,線程2也來查詢庫存,發現也是1,也進行了扣減(高並發的場景下)
這就導致了超賣的問題。
對於這種高並發的問題,最常見的解決方法就是:上鎖~
但鎖又包含悲觀鎖和樂觀鎖。
悲觀鎖簡單的講就是:覺得線程一定會發生,然後在操作之前每個人先拿鎖,你執行完後,在輪到下一個來執行(串行執行)
樂觀鎖:就是樂觀(認為線程安全一定不會發生),只要在每次對資料修改之前,判斷其他線程是否對資料進行的修改來保證線程安全。
悲觀鎖定較為簡單,這裡實作樂觀鎖定。
樂觀鎖的關鍵是判斷先前查詢得到的資料是否有被修改過,常見的方式有兩種
溫馨提示:左邊表格的資料都是執行緒1執行後的數據哦~
就是在查詢庫存的步驟上加上一個版本號,每次修改完資料後給版本號+1並在後面加上where條件判斷版本號碼是否和修改前的一致
這樣就可以做到執行緒安全啦~
//5.1扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id" , voucherId).gt("stock" ,0) .update();
package com.hmdp.service.impl; import com.hmdp.dto.Result; import com.hmdp.entity.SeckillVoucher; import com.hmdp.entity.VoucherOrder; import com.hmdp.mapper.VoucherOrderMapper; import com.hmdp.service.ISeckillVoucherService; import com.hmdp.service.IVoucherOrderService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.hmdp.utils.RedisIdWorker; import com.hmdp.utils.UserHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; /** * <p> * 服务实现类 * </p> */ @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService { @Resource private ISeckillVoucherService iSeckillVoucherService; @Resource private RedisIdWorker redisIdWorker; @Override public Result seckillVoucher(Long voucherId) { //1.获取优惠券信息 SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId); //2.判断是否已经开始 if (voucher.getBeginTime().isAfter(LocalDateTime.now())){ Result.fail("秒杀尚未开始!"); } //3.判断是否已经结束 if (voucher.getEndTime().isBefore(LocalDateTime.now())){ Result.fail("秒杀已经结束了!"); } //4.判断库存是否充足 if (voucher.getStock() < 1) { Result.fail("库存不充足!"); } //5.扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0) .update(); if (!success){ Result.fail("库存不充足!"); } //6. 创建订单 VoucherOrder voucherOrder = new VoucherOrder(); //6.1添加订单id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //6.2添加用户id Long userId = UserHolder.getUser().getId(); voucherOrder.setUserId(userId); //6.3添加优惠券id voucherOrder.setVoucherId(voucherId); save(voucherOrder); //7.返回订单id return Result.ok(orderId); } }
以上是Redis優惠券秒殺問題怎麼解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!