亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Redis解決優(yōu)惠券秒殺應(yīng)用案例

 更新時(shí)間:2022年11月01日 11:34:34   作者:兜兜轉(zhuǎn)轉(zhuǎn)m  
這篇文章主要介紹了Redis解決優(yōu)惠券秒殺應(yīng)用案例,本文先講了搶購(gòu)問(wèn)題,指出其中會(huì)出現(xiàn)的多線(xiàn)程問(wèn)題,提出解決方案采用悲觀(guān)鎖和樂(lè)觀(guān)鎖兩種方式進(jìn)行實(shí)現(xiàn),然后發(fā)現(xiàn)在搶購(gòu)過(guò)程中容易出現(xiàn)一人多單現(xiàn)象,需要的朋友可以參考下

雖然本文是針對(duì)黑馬點(diǎn)評(píng)的優(yōu)惠券秒殺業(yè)務(wù)的實(shí)現(xiàn),但是是適用于各種搶購(gòu)活動(dòng),保證線(xiàn)程安全。

摘要:本文先講了搶購(gòu)問(wèn)題,指出其中會(huì)出現(xiàn)的多線(xiàn)程問(wèn)題,提出解決方案采用悲觀(guān)鎖和樂(lè)觀(guān)鎖兩種方式進(jìn)行實(shí)現(xiàn),然后發(fā)現(xiàn)在搶購(gòu)過(guò)程中容易出現(xiàn)一人多單現(xiàn)象,為保證優(yōu)惠券不會(huì)被【黃?!繐尩?,因此我們?cè)?strong>保證多線(xiàn)程安全的情況下實(shí)現(xiàn)了一人一單業(yè)務(wù),最后指出本文的實(shí)現(xiàn)在集群情況下的不足之處。在本專(zhuān)欄的另一篇文章中提出集群或者分布式系統(tǒng)的解決方案。

【前端頁(yè)面】

 在代金券發(fā)放后,多個(gè)用戶(hù)會(huì)進(jìn)行優(yōu)惠券搶購(gòu),在搶購(gòu)時(shí)需要判斷兩點(diǎn):

下單時(shí)需要判斷兩點(diǎn):

  • 秒殺是否開(kāi)始或結(jié)束,如果尚未開(kāi)始或已經(jīng)結(jié)束則無(wú)法下單 
  • 庫(kù)存是否充足,不足則無(wú)法下單

下單核心邏輯分析:

當(dāng)用戶(hù)開(kāi)始進(jìn)行下單,我們應(yīng)當(dāng)去查詢(xún)優(yōu)惠卷信息,查詢(xún)到優(yōu)惠卷信息,判斷是否滿(mǎn)足秒殺條件

比如時(shí)間是否充足,如果時(shí)間充足,則進(jìn)一步判斷庫(kù)存是否足夠,如果兩者都滿(mǎn)足,則扣減庫(kù)存,創(chuàng)建訂單,然后返回訂單id,如果有一個(gè)條件不滿(mǎn)足則直接結(jié)束。

【邏輯圖】

 【代碼實(shí)現(xiàn)】

@Override
public Result seckillVoucher(Long voucherId) {
    // 1.查詢(xún)優(yōu)惠券
    SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
    // 2.判斷秒殺是否開(kāi)始
    if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
        // 尚未開(kāi)始
        return Result.fail("秒殺尚未開(kāi)始!");
    }
    // 3.判斷秒殺是否已經(jīng)結(jié)束
    if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
        // 尚未開(kāi)始
        return Result.fail("秒殺已經(jīng)結(jié)束!");
    }
    // 4.判斷庫(kù)存是否充足#######
    if (voucher.getStock() < 1) {
        // 庫(kù)存不足
        return Result.fail("庫(kù)存不足!");
    }
    //5,扣減庫(kù)存
    boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).update();
    if (!success) {
        //扣減庫(kù)存
        return Result.fail("庫(kù)存不足!");
    }
    //6.創(chuàng)建訂單
    VoucherOrder voucherOrder = new VoucherOrder();
    // 6.1.訂單id
    long orderId = redisIdWorker.nextId("order");
    voucherOrder.setId(orderId);
    // 6.2.用戶(hù)id
    Long userId = UserHolder.getUser().getId();
    voucherOrder.setUserId(userId);
    // 6.3.代金券id
    voucherOrder.setVoucherId(voucherId);
    save(voucherOrder);
 
    return Result.ok(orderId);
 
}

【分析代碼】

  • 從上述的邏輯圖中我們可以知道,要扣減庫(kù)存,并且要保存訂單,因此需要事務(wù)業(yè)務(wù)
  • 在第4步判斷庫(kù)存是否充足處,會(huì)出現(xiàn)多線(xiàn)程問(wèn)題。出現(xiàn)訂單超賣(mài)現(xiàn)象

問(wèn)題代碼如下:

 if (voucher.getStock() < 1) {
        // 庫(kù)存不足
        return Result.fail("庫(kù)存不足!");
    }
    //5,扣減庫(kù)存
    boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).update();
    if (!success) {
        //扣減庫(kù)存
        return Result.fail("庫(kù)存不足!");
    }

 【采用鎖】解決上述超賣(mài)問(wèn)題。

悲觀(guān)鎖:

悲觀(guān)鎖可以實(shí)現(xiàn)對(duì)于數(shù)據(jù)的串行化執(zhí)行,比如syn,和lock都是悲觀(guān)鎖的代表,同時(shí),悲觀(guān)鎖中又可以再細(xì)分為公平鎖,非公平鎖,可重入鎖,等等

樂(lè)觀(guān)鎖:

樂(lè)觀(guān)鎖:會(huì)有一個(gè)版本號(hào),每次操作數(shù)據(jù)會(huì)對(duì)版本號(hào)+1,再提交回?cái)?shù)據(jù)時(shí),會(huì)去校驗(yàn)是否比之前的版本大1 ,如果大1 ,則進(jìn)行操作成功,這套機(jī)制的核心邏輯在于,如果在操作過(guò)程中,版本號(hào)只比原來(lái)大1 ,那么就意味著操作過(guò)程中沒(méi)有人對(duì)他進(jìn)行過(guò)修改,他的操作就是安全的,如果不大1,則數(shù)據(jù)被修改過(guò),當(dāng)然樂(lè)觀(guān)鎖還有一些變種的處理方式比如cas

樂(lè)觀(guān)鎖的典型代表:就是cas,利用cas進(jìn)行無(wú)鎖化機(jī)制加鎖,var5 是操作前讀取的內(nèi)存值,while中的var1+var2 是預(yù)估值,如果預(yù)估值 == 內(nèi)存值,則代表中間沒(méi)有被人修改過(guò),此時(shí)就將新值去替換 內(nèi)存值

其中do while 是為了在操作失敗時(shí),再次進(jìn)行自旋操作,即把之前的邏輯再操作一次。

修改代碼方案

我們的樂(lè)觀(guān)鎖保證stock大于0 即可,如果查詢(xún)邏輯stock不能保證大于0,則會(huì)出現(xiàn) success為false我們?cè)诤笪倪M(jìn)行判斷即可。

boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).update().gt("stock",0); //where id = ? and stock > 0
   if (!success) {
        //扣減庫(kù)存
        return Result.fail("庫(kù)存不足!");
    }

代碼寫(xiě)到這里,我們就解決了多線(xiàn)程安全問(wèn)題(優(yōu)惠券超賣(mài))

一人一單

但是我們?cè)跈z查數(shù)據(jù)庫(kù)數(shù)據(jù)時(shí),我們發(fā)現(xiàn)一個(gè)人可以購(gòu)買(mǎi)多個(gè)優(yōu)惠券。

因此我們可以在搶購(gòu)前,判斷該用戶(hù)是否已經(jīng)購(gòu)買(mǎi)過(guò)該優(yōu)惠券,如果購(gòu)買(mǎi)過(guò)則直接返回。

【邏輯圖】紅框內(nèi)的是新增邏輯。

 @Override
public Result seckillVoucher(Long voucherId) {
    // 1.查詢(xún)優(yōu)惠券
    SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
    // 2.判斷秒殺是否開(kāi)始
    if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
        // 尚未開(kāi)始
        return Result.fail("秒殺尚未開(kāi)始!");
    }
    // 3.判斷秒殺是否已經(jīng)結(jié)束
    if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
        // 尚未開(kāi)始
        return Result.fail("秒殺已經(jīng)結(jié)束!");
    }
    // 4.判斷庫(kù)存是否充足
    if (voucher.getStock() < 1) {
        // 庫(kù)存不足
        return Result.fail("庫(kù)存不足!");
    }
    // 5.一人一單邏輯
    // 5.1.用戶(hù)id
    Long userId = UserHolder.getUser().getId();
    int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
    // 5.2.判斷是否存在
    if (count > 0) {
        // 用戶(hù)已經(jīng)購(gòu)買(mǎi)過(guò)了
        return Result.fail("用戶(hù)已經(jīng)購(gòu)買(mǎi)過(guò)一次!");
    }
 
    //6,扣減庫(kù)存
    boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).update();
    if (!success) {
        //扣減庫(kù)存
        return Result.fail("庫(kù)存不足!");
    }
    //7.創(chuàng)建訂單
    VoucherOrder voucherOrder = new VoucherOrder();
    // 7.1.訂單id
    long orderId = redisIdWorker.nextId("order");
    voucherOrder.setId(orderId);
 
    voucherOrder.setUserId(userId);
    // 7.3.代金券id
    voucherOrder.setVoucherId(voucherId);
    save(voucherOrder);
 
    return Result.ok(orderId);
 
}

 【分析代碼】---仍然會(huì)出現(xiàn)多線(xiàn)程問(wèn)題。

        存在問(wèn)題:現(xiàn)在的問(wèn)題還是和之前一樣,并發(fā)過(guò)來(lái),查詢(xún)數(shù)據(jù)庫(kù),都不存在訂單,所以我們還是需要加鎖,但是樂(lè)觀(guān)鎖比較適合更新數(shù)據(jù),而現(xiàn)在是插入數(shù)據(jù),所以我們需要使用悲觀(guān)鎖操作

【注意事項(xiàng)】

  • 事務(wù)應(yīng)該包含在鎖的內(nèi)部。
  • 鎖的粒度,鎖的對(duì)象應(yīng)該是用戶(hù)級(jí)別的,而不是整個(gè)搶購(gòu)優(yōu)惠券級(jí)別的,因此我們不會(huì)直接將synchronized加到方法上。
  • 鎖對(duì)象的細(xì)節(jié)處理,使用userId.toString().intern()保證對(duì)象唯一。
  • 獲取代理對(duì)象調(diào)用切入事務(wù)
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.RedisWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import javax.annotation.Resource;
import java.time.LocalDateTime;
 
/**
 * <p>
 * 服務(wù)實(shí)現(xiàn)類(lèi)
 * </p>
 *
 * @author msf
 * @since 2022-10-29
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
 
    @Resource
    private ISeckillVoucherService seckillVoucherService;
 
    @Resource
    private RedisWorker redisWorker;
 
 
    @Override
 
    public Result seckillVoucher(Long voucherId) {
        // 1. 查詢(xún)優(yōu)惠券信息
        SeckillVoucher voucherOrder = seckillVoucherService.getById(voucherId);
        // 2.判斷秒殺是否開(kāi)始
        if (voucherOrder.getBeginTime().isAfter(LocalDateTime.now())) {
            return Result.fail("搶購(gòu)尚未開(kāi)始");
        }
        if (voucherOrder.getEndTime().isBefore(LocalDateTime.now())) {
            return Result.fail("搶購(gòu)已經(jīng)結(jié)束");
        }
        // 3.判斷庫(kù)存是否充足
        if (voucherOrder.getStock() < 1) {
            return Result.fail("您來(lái)晚了,票已被搶完");
        }
        Long userId = UserHolder.getUser().getId();
        // 事務(wù)應(yīng)該在synchronized里面
        synchronized (userId.toString().intern()) {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId,userId);
        }
    }
 
 
    @Transactional
    public Result createVoucherOrder(Long voucherId,Long userId) {
            // 4. 一人一單邏輯
            // 4.1 根據(jù)優(yōu)惠券id和用戶(hù)id查詢(xún)訂單
            Integer count = query().eq("user_id", userId)
                    .eq("voucher_id", voucherId).count();
            // 4.2 訂單存在,直接返回
            if (count > 0) {
                return Result.fail("用戶(hù)已經(jīng)購(gòu)買(mǎi)一次");
            }
 
            // 5. 扣減庫(kù)存
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1")
                    .gt("stock", 0)
                    .eq("voucher_id", voucherId).update();
            if (!success) {
                return Result.fail("庫(kù)存不足");
            }
 
            // 6.創(chuàng)建訂單
            VoucherOrder order = new VoucherOrder();
            // 6.1 設(shè)置id
            order.setId(redisWorker.nextId("order"));
            // 6.2 設(shè)置訂單id
            order.setVoucherId(voucherId);
            // 6.3 設(shè)置用戶(hù)id
            order.setUserId(userId);
            save(order);
 
            // 7. 返回訂單id
            return Result.ok(order);
 
    }
}

展望

雖然我們利用鎖和事務(wù)解決單體系統(tǒng)下的秒殺功能,但是現(xiàn)在的業(yè)務(wù)一般是在集群和分布式系統(tǒng)協(xié)作完成,因此我們?cè)跍y(cè)試系統(tǒng)在集群部署時(shí),仍會(huì)出現(xiàn)一人多單問(wèn)題,稍后我們將更新文章,分析問(wèn)題出現(xiàn)原因,并利用分布式鎖的方式解決該問(wèn)題。

到此這篇關(guān)于Redis解決優(yōu)惠券秒殺的文章就介紹到這了,更多相關(guān)Redis優(yōu)惠券秒殺內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis整合Lua腳本的實(shí)現(xiàn)操作

    Redis整合Lua腳本的實(shí)現(xiàn)操作

    Redis對(duì)lua腳本的支持是從Redis2.6.0版本開(kāi)始引入的,它可以讓用戶(hù)在Redis服務(wù)器內(nèi)置的Lua解釋器中執(zhí)行指定的lua腳本,本文就來(lái)介紹一下Redis整合Lua腳本的實(shí)現(xiàn),感興趣的可以了解一下
    2024-03-03
  • Redis 使用跳表實(shí)現(xiàn)有序集合的方法

    Redis 使用跳表實(shí)現(xiàn)有序集合的方法

    Redis有序集合底層為什么使用跳表而非其他數(shù)據(jù)結(jié)構(gòu)如平衡樹(shù)、紅黑樹(shù)或B+樹(shù)的原因在于其特殊的設(shè)計(jì)和應(yīng)用場(chǎng)景,跳表提供了與平衡樹(shù)類(lèi)似的效率,同時(shí)實(shí)現(xiàn)更簡(jiǎn)單,調(diào)試和修改也更加容易,感興趣的朋友一起看看吧
    2024-09-09
  • Redis字符串原理的深入理解

    Redis字符串原理的深入理解

    這篇文章主要給大家介紹了關(guān)于Redis字符串原理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Windows下Redis的安裝使用圖解

    Windows下Redis的安裝使用圖解

    Redis是一個(gè)key-value存儲(chǔ)系統(tǒng)。Redis的出現(xiàn),很大程度補(bǔ)償了memcached這類(lèi)key/value存儲(chǔ)的不足,在部分場(chǎng)合可以對(duì)關(guān)系數(shù)據(jù)庫(kù)起到很好的補(bǔ)充作用。這篇文章小編為大家分享了在Windows下進(jìn)行安裝和使用Redis的技巧。
    2015-09-09
  • 利用Redis實(shí)現(xiàn)SQL伸縮的方法

    利用Redis實(shí)現(xiàn)SQL伸縮的方法

    本文主要介紹了如何通過(guò)鎖和時(shí)間序列等方面來(lái)提升傳統(tǒng)數(shù)據(jù)庫(kù)的性能等方法,利用Redis實(shí)現(xiàn)SQL伸縮,供有需要的朋友們參考。
    2015-09-09
  • Redis?Lua腳本實(shí)現(xiàn)ip限流示例

    Redis?Lua腳本實(shí)現(xiàn)ip限流示例

    這篇文章主要介紹了Redis?Lua腳本實(shí)現(xiàn)ip限流示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • Redis教程之代理ip池設(shè)計(jì)方法詳解

    Redis教程之代理ip池設(shè)計(jì)方法詳解

    這篇文章主要介紹了Redis實(shí)現(xiàn)代理ip池的設(shè)計(jì)方法,文中給出了詳細(xì)的介紹與示例代碼,相信對(duì)大家的理解和學(xué)習(xí)具有一定的參考借鑒價(jià)值,有需要的朋友們下面來(lái)一起看看吧。
    2017-01-01
  • redis.config配置文件

    redis.config配置文件

    在使用Redis時(shí),我們通常需要對(duì)Redis進(jìn)行一些配置,以確保其能夠正常運(yùn)行并滿(mǎn)足我們的需求,本文主要介紹了redis.config配置文件,感興趣的可以了解一下
    2023-11-11
  • redis主從切換導(dǎo)致的數(shù)據(jù)丟失與陷入只讀狀態(tài)故障解決方案

    redis主從切換導(dǎo)致的數(shù)據(jù)丟失與陷入只讀狀態(tài)故障解決方案

    這篇文章主要介紹了redis主從切換導(dǎo)致的數(shù)據(jù)丟失與陷入只讀狀態(tài)故障解決方案的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • Redis生成分布式系統(tǒng)全局唯一ID的實(shí)現(xiàn)

    Redis生成分布式系統(tǒng)全局唯一ID的實(shí)現(xiàn)

    在互聯(lián)網(wǎng)系統(tǒng)中,并發(fā)越大的系統(tǒng),數(shù)據(jù)就越大,數(shù)據(jù)越大就越需要分布式,本文主要介紹了Redis生成分布式系統(tǒng)全局唯一ID的實(shí)現(xiàn),感興趣的可以了解一下
    2021-10-10

最新評(píng)論