溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Redis優(yōu)惠券秒殺問題怎么解決

發(fā)布時間:2022-12-07 09:48:21 來源:億速云 閱讀:111 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Redis優(yōu)惠券秒殺問題怎么解決”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Redis優(yōu)惠券秒殺問題怎么解決”吧!

1 實現(xiàn)優(yōu)惠券秒殺功能

Redis優(yōu)惠券秒殺問題怎么解決

下單時需要判斷兩點:1.秒殺是否開始或者結(jié)束2.庫存是否充足

所以,我們的業(yè)務(wù)邏輯如下

1. 通過優(yōu)惠券id獲取優(yōu)惠券信息

2.判斷秒殺是否開始,如果未返回錯誤信息

3.判斷秒殺是否結(jié)束,如果已經(jīng)結(jié)束返回錯誤信息

4.如果在秒殺時間內(nèi),判斷庫存是否充足

5.如果充足,扣減庫存

6.創(chuàng)建訂單信息,并保存到優(yōu)惠券訂單表中

6.1 保存訂單id

6.2保存用戶id

6.3保存優(yōu)惠券id

7.返回訂單id

Redis優(yōu)惠券秒殺問題怎么解決

代碼實現(xiàn):(Service層實現(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ù)實現(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 超賣問題(重點)

我們先嘗試在高并發(fā)的情況下運行上述代碼。(使用jmx工具)

下圖是創(chuàng)建了兩百個線程,在一瞬間發(fā)出優(yōu)惠券請求

Redis優(yōu)惠券秒殺問題怎么解決

但是我們看聚合報告,發(fā)現(xiàn)異常值只有45.5%,按道理來說應(yīng)該是50%(因為庫存有100個,這里發(fā)出了200個請求)

Redis優(yōu)惠券秒殺問題怎么解決

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

Redis優(yōu)惠券秒殺問題怎么解決

訂單也是添加了109個,這顯然發(fā)生了超賣的問題。

那么,為什么會發(fā)生這種問題呢?

看圖說話:

按照我們正常的流程來走,就是線程1線查詢完庫存,然后扣減庫存,這個時候線程2再來查詢庫存,扣減庫存,這樣是沒問題的。

Redis優(yōu)惠券秒殺問題怎么解決

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

Redis優(yōu)惠券秒殺問題怎么解決

這就導致了超賣的問題。

對于這種高并發(fā)的問題,最常見的解決方法就是:上鎖~

但鎖又包括悲觀鎖和樂觀鎖。

悲觀鎖簡單的講就是:覺得線程一定會發(fā)生,然后在操作之前每個人先拿鎖,你執(zhí)行完后,在輪到下一個來執(zhí)行(串行執(zhí)行)

樂觀鎖 :就是樂觀(認為線程安全一定不會發(fā)生),只要在每次對數(shù)據(jù)修改之前,判斷其他線程是否對數(shù)據(jù)進行的修改來保證線程安全。

Redis優(yōu)惠券秒殺問題怎么解決

悲觀鎖較為簡單,這里實現(xiàn)樂觀鎖。

樂觀鎖的關(guān)鍵是判斷之前查詢得到的數(shù)據(jù)是否有被修改過,常見的方式有兩種

溫馨提示:左邊表格的數(shù)據(jù)都是線程1執(zhí)行后的數(shù)據(jù)哦~

1.版本號法

就是在查詢庫存的步驟上加上一個版本號,每次修改完數(shù)據(jù)后給版本號+1并在后面加上where條件判斷版本號是否和修改前的一致

Redis優(yōu)惠券秒殺問題怎么解決

這樣就可以做到線程安全啦~

2.CAS法

這個就是不用版本號了,直接在修改數(shù)據(jù)庫后加上where條件判斷庫存是否是修改前的庫存

Redis優(yōu)惠券秒殺問題怎么解決

解決超賣問題代碼實現(xiàn):

說到底就是在我們扣減庫存的時候加上一個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ù)實現(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);
    }
}

到此,相信大家對“Redis優(yōu)惠券秒殺問題怎么解決”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI