Redis優(yōu)惠券秒殺解決方案
1 實(shí)現(xiàn)優(yōu)惠券秒殺功能

下單時(shí)需要判斷兩點(diǎn):1.秒殺是否開始或者結(jié)束2.庫存是否充足
所以,我們的業(yè)務(wù)邏輯如下
1. 通過優(yōu)惠券id獲取優(yōu)惠券信息
2.判斷秒殺是否開始,如果未返回錯(cuò)誤信息
3.判斷秒殺是否結(jié)束,如果已經(jīng)結(jié)束返回錯(cuò)誤信息
4.如果在秒殺時(shí)間內(nèi),判斷庫存是否充足
5.如果充足,扣減庫存
6.創(chuàng)建訂單信息,并保存到優(yōu)惠券訂單表中
6.1 保存訂單id
6.2保存用戶id
6.3保存優(yōu)惠券id
7.返回訂單id

代碼實(shí)現(xiàn):(Service層實(shí)現(xiàn)類)
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>
* 服務(wù)實(shí)現(xiàn)類
* </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.獲取優(yōu)惠券信息
SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
//2.判斷是否已經(jīng)開始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
Result.fail("秒殺尚未開始!");
}
//3.判斷是否已經(jīng)結(jié)束
if (voucher.getEndTime().isBefore(LocalDateTime.now())){
Result.fail("秒殺已經(jīng)結(jié)束了!");
}
//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. 創(chuàng)建訂單
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添加優(yōu)惠券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//7.返回訂單id
return Result.ok(orderId);
}
}2 超賣問題(重點(diǎn))
我們先嘗試在高并發(fā)的情況下運(yùn)行上述代碼。(使用jmx工具)
下圖是創(chuàng)建了兩百個(gè)線程,在一瞬間發(fā)出優(yōu)惠券請(qǐng)求

但是我們看聚合報(bào)告,發(fā)現(xiàn)異常值只有45.5%,按道理來說應(yīng)該是50%(因?yàn)閹齑嬗?00個(gè),這里發(fā)出了200個(gè)請(qǐng)求)

一看庫存數(shù),好家伙,是-9

訂單也是添加了109個(gè),這顯然發(fā)生了超賣的問題。
那么,為什么會(huì)發(fā)生這種問題呢?
看圖說話:
按照我們正常的流程來走,就是線程1線查詢完庫存,然后扣減庫存,這個(gè)時(shí)候線程2再來查詢庫存,扣減庫存,這樣是沒問題的。

超賣的問題就出在,在訂單1查詢庫存后,發(fā)現(xiàn)是1,但還沒去扣減的時(shí)候,線程2也來查詢庫存,發(fā)現(xiàn)也是1,也進(jìn)行了扣減(高并發(fā)的場(chǎng)景下)

這就導(dǎo)致了超賣的問題。
對(duì)于這種高并發(fā)的問題,最常見的解決方法就是:上鎖~
但鎖又包括悲觀鎖和樂觀鎖。
悲觀鎖簡(jiǎn)單的講就是:覺得線程一定會(huì)發(fā)生,然后在操作之前每個(gè)人先拿鎖,你執(zhí)行完后,在輪到下一個(gè)來執(zhí)行(串行執(zhí)行)
樂觀鎖 :就是樂觀(認(rèn)為線程安全一定不會(huì)發(fā)生),只要在每次對(duì)數(shù)據(jù)修改之前,判斷其他線程是否對(duì)數(shù)據(jù)進(jìn)行的修改來保證線程安全。

悲觀鎖較為簡(jiǎn)單,這里實(shí)現(xiàn)樂觀鎖。
樂觀鎖的關(guān)鍵是判斷之前查詢得到的數(shù)據(jù)是否有被修改過,常見的方式有兩種
溫馨提示:左邊表格的數(shù)據(jù)都是線程1執(zhí)行后的數(shù)據(jù)哦~
1.版本號(hào)法
就是在查詢庫存的步驟上加上一個(gè)版本號(hào),每次修改完數(shù)據(jù)后給版本號(hào)+1并在后面加上where條件判斷版本號(hào)是否和修改前的一致

這樣就可以做到線程安全啦~
2.CAS法
這個(gè)就是不用版本號(hào)了,直接在修改數(shù)據(jù)庫后加上where條件判斷庫存是否是修改前的庫存

解決超賣問題代碼實(shí)現(xiàn):
說到底就是在我們扣減庫存的時(shí)候加上一個(gè)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>
* 服務(wù)實(shí)現(xiàn)類
* </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.獲取優(yōu)惠券信息
SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
//2.判斷是否已經(jīng)開始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
Result.fail("秒殺尚未開始!");
}
//3.判斷是否已經(jīng)結(jié)束
if (voucher.getEndTime().isBefore(LocalDateTime.now())){
Result.fail("秒殺已經(jīng)結(jié)束了!");
}
//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. 創(chuàng)建訂單
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添加優(yōu)惠券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//7.返回訂單id
return Result.ok(orderId);
}
}超賣問題解決
到此這篇關(guān)于Redis優(yōu)惠券秒殺解決方案的文章就介紹到這了,更多相關(guān)Redis優(yōu)惠券秒殺內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
大白話講解調(diào)用Redis的increment失敗原因及推薦使用詳解
本文主要介紹了調(diào)用Redis的increment失敗原因及推薦使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Redis實(shí)現(xiàn)短信登錄的企業(yè)實(shí)戰(zhàn)
本文主要介紹了Redis實(shí)現(xiàn)短信登錄的企業(yè)實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Redis實(shí)現(xiàn)信息已讀未讀狀態(tài)提示
這篇文章主要介紹了Redis實(shí)現(xiàn)信息已讀未讀狀態(tài)提示的相關(guān)資料,需要的朋友可以參考下2016-04-04
完美解決Redis在雙擊redis-server.exe出現(xiàn)閃退問題
本文主要介紹了完美解決Redis在雙擊redis-server.exe出現(xiàn)閃退問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
使用AOP+redis+lua做方法限流的實(shí)現(xiàn)
本文主要介紹了使用AOP+redis+lua做方法限流的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
redis.clients.jedis.exceptions.JedisDataException:?NOAUTH?
本文主要介紹了redis.clients.jedis.exceptions.JedisDataException:?NOAUTH?Authentication?required數(shù)據(jù)操作異常的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05
Redis 緩存實(shí)現(xiàn)存儲(chǔ)和讀取歷史搜索關(guān)鍵字的操作方法
這篇文章主要介紹了Redis 緩存實(shí)現(xiàn)存儲(chǔ)和讀取歷史搜索關(guān)鍵字,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
RabbitMQ+redis+Redisson分布式鎖+seata實(shí)現(xiàn)訂單服務(wù)的流程分析
訂單服務(wù)涉及許多方面,分布式事務(wù),分布式鎖,例如訂單超時(shí)未支付要取消訂單,訂單如何防止重復(fù)提交,如何防止超賣、這里都會(huì)使用到,這篇文章主要介紹了RabbitMQ+redis+Redisson分布式鎖+seata實(shí)現(xiàn)訂單服務(wù)的流程分析,需要的朋友可以參考下2024-07-07

