<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
下單時需要判斷兩點: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條件判斷版本號是否和修改前的一致
這樣就可以做到執行緒安全啦~
這個就是不用版本號了,直接在修改資料庫後加上where條件判斷庫存是否是修改前的庫存
解決超賣問題程式碼實現:
說到底就是在我們扣減庫存的時候加上一個where條件判斷庫存是否大於0
//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優惠券秒殺解決方案的文章就介紹到這了,更多相關Redis優惠券秒殺內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45