最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis實現(xiàn)優(yōu)惠券限一單限制詳解

 更新時間:2022年12月06日 16:19:43   作者:芝麻干  
這篇文章主要介紹了Redis解決優(yōu)惠券秒殺應(yīng)用案例,本文先講了搶購問題,指出其中會出現(xiàn)的多線程問題,提出解決方案采用悲觀鎖和樂觀鎖兩種方式進(jìn)行實現(xiàn),然后發(fā)現(xiàn)在搶購過程中容易出現(xiàn)一人多單現(xiàn)象,需要的朋友可以參考下

需求:修改秒殺業(yè)務(wù),要求同一個優(yōu)惠券,一個用戶只能下一單

我們只需要在增加訂單之前,拿用戶id和優(yōu)惠券id判斷訂單是否已經(jīng)存在,如果存在,說明用戶已經(jīng)購買。

代碼實現(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 org.springframework.transaction.annotation.Transactional;
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("庫存不充足!");
        }
         Long userId = UserHolder.getUser().getId();
        //6.根據(jù)優(yōu)惠券id和用戶id判斷訂單是否已經(jīng)存在
        //如果存在,則返回錯誤信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用戶已經(jīng)購買!");
        }
        //7. 創(chuàng)建訂單
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加訂單id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用戶id
        voucherOrder.setUserId(userId);
        //7.3添加優(yōu)惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回訂單id
        return Result.ok(orderId);
    }
}

但是,還沒完,這種代碼邏輯,在高并發(fā)的情況下還是會出現(xiàn)一個人購買購買多個的情況:

就是同一時間,多個線程來查詢數(shù)據(jù),都沒有查到訂單,都去創(chuàng)建了訂單(高并發(fā)的情況下)

類似超賣問題,所以我們要進(jìn)行上鎖。

這次就用悲觀鎖。

最簡單的實現(xiàn)方法,就是把從查詢訂單是否存在到保存訂單返回訂單id這一段代碼塊進(jìn)行封裝成一個方法,然后在這個方法上加上synchronized關(guān)鍵字和spring事務(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.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
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("庫存不充足!");
        }
        return createVoucherOrder(voucherId);
    }
    @Transactional
    public synchronized Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根據(jù)優(yōu)惠券id和用戶id判斷訂單是否已經(jīng)存在
        //如果存在,則返回錯誤信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用戶已經(jīng)購買!");
        }
        //7. 創(chuàng)建訂單
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加訂單id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用戶id
        voucherOrder.setUserId(userId);
        //7.3添加優(yōu)惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回訂單id
        return Result.ok(orderId);
    }
}

但是,這個方法就是使用了悲觀鎖,鎖的對象是整個類對象,所有用戶公用一把鎖,就會導(dǎo)致串行執(zhí)行,從而性能大大降低。

我們可以只鎖上用戶id,讓他每個用戶獲得一把鎖。

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 org.springframework.transaction.annotation.Transactional;
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("庫存不充足!");
        }
        Long userId = UserHolder.getUser().getId();
        return createVoucherOrder(voucherId);
    }
    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根據(jù)優(yōu)惠券id和用戶id判斷訂單是否已經(jīng)存在
        synchronized (userId.toString().intern()){
            //如果存在,則返回錯誤信息
            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            if (count > 0) {
                return Result.fail("用戶已經(jīng)購買!");
            }
            //7. 創(chuàng)建訂單
            VoucherOrder voucherOrder = new VoucherOrder();
            //7.1添加訂單id
            Long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //7.2添加用戶id
            voucherOrder.setUserId(userId);
            //7.3添加優(yōu)惠券id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //8.返回訂單id
            return Result.ok(orderId);
        } 
    }
}

這里鎖上userid時,除了用toString方法轉(zhuǎn)成字符串,還使用intern方法的原因是:

toString方法的底層原理其實是new一個String對象,然后將其變成字符串,如果只鎖上了加toString方法的userid,就有可能出現(xiàn)相同的userid,但是toString底層new出來的String對象不同,而多分了鎖。所以使用intern方法來直接判斷常量池中的string值是否一致,值一樣的共用一把鎖,這樣就不會導(dǎo)致多分鎖了。

但是但是,還沒完因為這里我們是加了鎖和事務(wù),但是因為這個事務(wù)時Spring進(jìn)行管理的,它會在我們代碼塊結(jié)束后才會去執(zhí)行事務(wù),也就是我們釋放鎖的時候,才會執(zhí)行事務(wù)。這個時候,鎖放開了,就會有其他線程進(jìn)來,就很有可能出現(xiàn)事務(wù)提交帶上了其他線程。

我們可以這樣進(jìn)行改進(jìn):在本個方法上進(jì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 org.springframework.transaction.annotation.Transactional;
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("庫存不充足!");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()){
            return createVoucherOrder(voucherId);
        }
    }
    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根據(jù)優(yōu)惠券id和用戶id判斷訂單是否已經(jīng)存在
        //如果存在,則返回錯誤信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用戶已經(jīng)購買!");
        }
        //7. 創(chuàng)建訂單
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加訂單id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用戶id
        voucherOrder.setUserId(userId);
        //7.3添加優(yōu)惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回訂單id
        return Result.ok(orderId);
    }
}

但是但是但是,還沒完。哈哈

我們只給創(chuàng)建訂單這個方法(createVoucherOrder)加了事務(wù),但是沒給上面判斷條件的方法加上事務(wù),而我們鎖代碼塊里執(zhí)行的方法,其實是this.createVoucherOrder()方法,是沒有加事務(wù)的方法調(diào)用的createVoucherOrder()方法,這個this可不是spring的事務(wù)代理對象,這就會導(dǎo)致事務(wù)失效。

解決方法就是,我們只需要拿到代理對象,然后通過代理對象調(diào)用我們這個加了事務(wù)的方法,也就是createVoucherOrder()方法。

使用 AopContext.currentProxy();方法來拿到代理對象

溫馨提示 :使用這個方法前要先做兩件事~

1. 記得在配置類似加上@EnableAspectJAutoProxy(exposeProxy = true)注解來暴露這個代理對象

2. 加上依賴:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

完整代碼;:

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.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
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("庫存不充足!");
        }
        Long userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()){
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }
    }
    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根據(jù)優(yōu)惠券id和用戶id判斷訂單是否已經(jīng)存在
        //如果存在,則返回錯誤信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用戶已經(jīng)購買!");
        }
        //7. 創(chuàng)建訂單
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加訂單id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用戶id
        voucherOrder.setUserId(userId);
        //7.3添加優(yōu)惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回訂單id
        return Result.ok(orderId);
    }
}

到此這篇關(guān)于Redis實現(xiàn)優(yōu)惠券限一單限制詳解的文章就介紹到這了,更多相關(guān)Redis優(yōu)惠券內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis消息隊列實現(xiàn)秒殺教程

    Redis消息隊列實現(xiàn)秒殺教程

    這篇文章主要介紹了Redis消息隊列實現(xiàn)秒殺教程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • 一文教你學(xué)會Redis的事務(wù)

    一文教你學(xué)會Redis的事務(wù)

    Redis?作為內(nèi)存的存儲中間件,已經(jīng)是面試的面試題必問之一了。今天小編就來和大家一起來聊聊Redis的事務(wù)吧,希望對大家有所幫助
    2022-08-08
  • Redis如何解決BigKey

    Redis如何解決BigKey

    在Redis的使用過程中,我們經(jīng)常會遇到BigKey, BigKey的大值會導(dǎo)致Redis內(nèi)存中產(chǎn)生大量不連續(xù)的碎片,降低內(nèi)存利用效率,本文主要介紹了Redis如何解決BigKey,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • 淺談Redis哨兵模式高可用解決方案

    淺談Redis哨兵模式高可用解決方案

    Redis高可用有兩種模式:哨兵模式和集群模式,本文基于哨兵模式搭建一主兩從三哨兵Redis高可用服務(wù),感興趣的可以了解一下
    2022-03-03
  • Redis實現(xiàn)信息已讀未讀狀態(tài)提示

    Redis實現(xiàn)信息已讀未讀狀態(tài)提示

    這篇文章主要介紹了Redis實現(xiàn)信息已讀未讀狀態(tài)提示的相關(guān)資料,需要的朋友可以參考下
    2016-04-04
  • Redis序列化轉(zhuǎn)換類型報錯的解決

    Redis序列化轉(zhuǎn)換類型報錯的解決

    本文主要介紹了Redis序列化轉(zhuǎn)換類型報錯的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Redis實現(xiàn)每日簽到功能(大數(shù)據(jù)量)

    Redis實現(xiàn)每日簽到功能(大數(shù)據(jù)量)

    在面對百萬級用戶簽到情況下,傳統(tǒng)數(shù)據(jù)庫存儲和判斷會遇到瓶頸,使用Redis的二進(jìn)制數(shù)據(jù)類型可實現(xiàn)高效的簽到功能,示例代碼展示了如何調(diào)用這些功能,包括當(dāng)天簽到、補(bǔ)簽以及查詢簽到記錄,PHP結(jié)合Redis二進(jìn)制數(shù)據(jù)類型可有效處理大數(shù)據(jù)量下的簽到問題
    2024-10-10
  • Redis實現(xiàn)編碼生成規(guī)則方式

    Redis實現(xiàn)編碼生成規(guī)則方式

    在自動生成編碼時應(yīng)采用“MD+年月日+4位序列號”的規(guī)則,如“MD202310130001”,為避免使用隨機(jī)序列號導(dǎo)致的重復(fù)編碼,建議使用從0開始的自增序列號,此外,使用Redis的incrBy功能實現(xiàn)序列號自增,可以有效提高效率和降低實現(xiàn)難度
    2023-01-01
  • Linux系統(tǒng)下安裝Redis數(shù)據(jù)庫過程

    Linux系統(tǒng)下安裝Redis數(shù)據(jù)庫過程

    大家好,本篇文章主要講的是Linux系統(tǒng)下安裝Redis數(shù)據(jù)庫過程,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • 在項目中使用redis做緩存的一些思路

    在項目中使用redis做緩存的一些思路

    這篇文章主要介紹了在項目中使用redis做緩存的一些思路,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09

最新評論

锡林郭勒盟| 武冈市| 仙桃市| 吉水县| 长沙市| 庆云县| 郎溪县| 河间市| 凉山| 运城市| 克东县| 鹰潭市| 集贤县| 万宁市| 建昌县| 墨竹工卡县| 兴隆县| 中西区| 昔阳县| 炉霍县| 库伦旗| 广河县| 昌乐县| 南昌市| 罗山县| 江门市| 平塘县| 芦溪县| 青浦区| 闸北区| 桓台县| 保康县| 南乐县| 木里| 冀州市| 霍州市| 乌拉特中旗| 沂源县| 利辛县| 株洲县| 韶山市|