iis服务器助手广告
返回顶部
首页 > 资讯 > 精选 >Redis如何实现优惠券限一单限制
  • 237
分享到

Redis如何实现优惠券限一单限制

2023-07-04 18:07:49 237人浏览 泡泡鱼
摘要

本篇内容介绍了“Redis如何实现优惠券限一单限制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!需求:修改秒杀业务,要求同一个优惠券,一个用

本篇内容介绍了“Redis如何实现优惠券限一单限制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单

我们只需要在增加订单之前,拿用户id和优惠券id判断订单是否已经存在,如果存在,说明用户已经购买。

Redis如何实现优惠券限一单限制

代码实现:

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;@Servicepublic 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("库存不充足!");        }         Long userId = UserHolder.getUser().getId();        //6.根据优惠券id和用户id判断订单是否已经存在        //如果存在,则返回错误信息        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();        if (count > 0) {            return Result.fail("用户已经购买!");        }        //7. 创建订单        VoucherOrder voucherOrder = new VoucherOrder();        //7.1添加订单id        Long orderId = redisIdWorker.nextId("order");        voucherOrder.setId(orderId);        //7.2添加用户id        voucherOrder.setUserId(userId);        //7.3添加优惠券id        voucherOrder.setVoucherId(voucherId);        save(voucherOrder);        //8.返回订单id        return Result.ok(orderId);    }}

但是,还没完,这种代码逻辑,在高并发的情况下还是会出现一个人购买购买多个的情况:

就是同一时间,多个线程来查询数据,都没有查到订单,都去创建了订单(高并发的情况下)

类似超卖问题,所以我们要进行上

这次就用悲观锁。

最简单的实现方法,就是把从查询订单是否存在到保存订单返回订单id这一段代码块进行封装成一个方法,然后在这个方法上加上synchronized关键字和spring事务

如下:

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;@Servicepublic 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("库存不充足!");        }        return createVoucherOrder(voucherId);    }    @Transactional    public synchronized Result createVoucherOrder(Long voucherId) {        Long userId = UserHolder.getUser().getId();        //6.根据优惠券id和用户id判断订单是否已经存在        //如果存在,则返回错误信息        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();        if (count > 0) {            return Result.fail("用户已经购买!");        }        //7. 创建订单        VoucherOrder voucherOrder = new VoucherOrder();        //7.1添加订单id        Long orderId = redisIdWorker.nextId("order");        voucherOrder.setId(orderId);        //7.2添加用户id        voucherOrder.setUserId(userId);        //7.3添加优惠券id        voucherOrder.setVoucherId(voucherId);        save(voucherOrder);        //8.返回订单id        return Result.ok(orderId);    }}

但是,这个方法就是使用了悲观锁,锁的对象是整个类对象,所有用户公用一把锁,就会导致串行执行,从而性能大大降低。

我们可以只锁上用户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;@Servicepublic 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("库存不充足!");        }        Long userId = UserHolder.getUser().getId();        return createVoucherOrder(voucherId);    }    @Transactional    public Result createVoucherOrder(Long voucherId) {        Long userId = UserHolder.getUser().getId();        //6.根据优惠券id和用户id判断订单是否已经存在        synchronized (userId.toString().intern()){            //如果存在,则返回错误信息            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();            if (count > 0) {                return Result.fail("用户已经购买!");            }            //7. 创建订单            VoucherOrder voucherOrder = new VoucherOrder();            //7.1添加订单id            Long orderId = redisIdWorker.nextId("order");            voucherOrder.setId(orderId);            //7.2添加用户id            voucherOrder.setUserId(userId);            //7.3添加优惠券id            voucherOrder.setVoucherId(voucherId);            save(voucherOrder);            //8.返回订单id            return Result.ok(orderId);        }     }}

这里锁上userid时,除了用toString方法转成字符串,还使用intern方法的原因是:

toString方法的底层原理其实是new一个String对象,然后将其变成字符串,如果只锁上了加toString方法的userid,就有可能出现相同的userid,但是toString底层new出来的String对象不同,而多分了锁。所以使用intern方法来直接判断常量池中的string值是否一致,值一样的共用一把锁,这样就不会导致多分锁了。

但是但是,还没完因为这里我们是加了锁和事务,但是因为这个事务时Spring进行管理的,它会在我们代码块结束后才会去执行事务,也就是我们释放锁的时候,才会执行事务。这个时候,锁放开了,就会有其他线程进来,就很有可能出现事务提交带上了其他线程。

我们可以这样进行改进:在本个方法上进行加锁。

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;@Servicepublic 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("库存不充足!");        }        Long userId = UserHolder.getUser().getId();        synchronized (userId.toString().intern()){            return createVoucherOrder(voucherId);        }    }    @Transactional    public Result createVoucherOrder(Long voucherId) {        Long userId = UserHolder.getUser().getId();        //6.根据优惠券id和用户id判断订单是否已经存在        //如果存在,则返回错误信息        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();        if (count > 0) {            return Result.fail("用户已经购买!");        }        //7. 创建订单        VoucherOrder voucherOrder = new VoucherOrder();        //7.1添加订单id        Long orderId = redisIdWorker.nextId("order");        voucherOrder.setId(orderId);        //7.2添加用户id        voucherOrder.setUserId(userId);        //7.3添加优惠券id        voucherOrder.setVoucherId(voucherId);        save(voucherOrder);        //8.返回订单id        return Result.ok(orderId);    }}

我们只给创建订单这个方法(createVoucherOrder)加了事务,但是没给上面判断条件的方法加上事务,而我们锁代码块里执行的方法,其实是this.createVoucherOrder()方法,是没有加事务的方法调用的createVoucherOrder()方法,这个this可不是spring的事务代理对象,这就会导致事务失效。

解决方法就是,我们只需要拿到代理对象,然后通过代理对象调用我们这个加了事务的方法,也就是createVoucherOrder()方法。

使用 aopContext.currentProxy();方法来拿到代理对象

温馨提示 :使用这个方法前要先做两件事~

记得在配置类似加上@EnableAspectJAutoProxy(exposeProxy = true)注解来暴露这个代理对象

加上依赖:

        <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;@Servicepublic 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("库存不充足!");        }        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.根据优惠券id和用户id判断订单是否已经存在        //如果存在,则返回错误信息        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();        if (count > 0) {            return Result.fail("用户已经购买!");        }        //7. 创建订单        VoucherOrder voucherOrder = new VoucherOrder();        //7.1添加订单id        Long orderId = redisIdWorker.nextId("order");        voucherOrder.setId(orderId);        //7.2添加用户id        voucherOrder.setUserId(userId);        //7.3添加优惠券id        voucherOrder.setVoucherId(voucherId);        save(voucherOrder);        //8.返回订单id        return Result.ok(orderId);    }}

“Redis如何实现优惠券限一单限制”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: Redis如何实现优惠券限一单限制

本文链接: https://www.lsjlt.com/news/346712.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Redis如何实现优惠券限一单限制
    本篇内容介绍了“Redis如何实现优惠券限一单限制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!需求:修改秒杀业务,要求同一个优惠券,一个用...
    99+
    2023-07-04
  • Redis实现优惠券限一单限制详解
    需求:修改秒杀业务,要求同一个优惠券,一个用户只能下一单 我们只需要在增加订单之前,拿用户id和优惠券id判断订单是否已经存在,如果存在,说明用户已经购买。 代码实现: package com.hmdp.service...
    99+
    2022-12-06
    Redis优惠券限制 Redis优惠卷
  • redis如何实现限流
    redis实现限流的示例:使用接口实现,接口代码如下:#指定用户user_id的某个行为action_key在特定的时间内period只允许发生最多的次数max_countdef is_action_al lowed(u...
    99+
    2024-04-02
  • Java+MySQL如何实现设计优惠券系统
    这篇文章主要介绍“Java+MySQL如何实现设计优惠券系统”,在日常操作中,相信很多人在Java+MySQL如何实现设计优惠券系统问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java+MySQL如何实现设...
    99+
    2023-06-30
  • 如何实现限制SSH单用户登录
    这篇文章给大家分享的是有关如何实现限制SSH单用户登录的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。使用方法: 用root权限在后台(比如screen里)运行这个脚本代码如下:#!/usr/bin/perl&nb...
    99+
    2023-06-13
  • redis如何实现分布式限流
    Redis可以使用令牌桶算法来实现分布式限流。令牌桶算法是一种常用的限流算法,它通过维护一个固定容量的令牌桶,每秒钟往桶里放入一定数...
    99+
    2023-09-09
    redis
  • Spring Boot 整合Redis 实现优惠卷秒杀 一人一单功能
    目录一、什么是全局唯一ID⛅全局唯一ID⚡Redis实现全局唯一ID二、环境准备三、实现秒杀下单四、库存超卖问题⏳问题分析⌚ 乐观锁解决库存超卖✅Jmeter 测试五、优惠卷秒杀 实...
    99+
    2024-04-02
  • redis lua限流算法如何实现
    本篇内容介绍了“redis lua限流算法如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!限流算法常见的限流算法计数器算法漏...
    99+
    2023-07-02
  • Redis中如何实现限流策略
    这篇文章将为大家详细讲解有关Redis中如何实现限流策略,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、简单的限流基本原理当系统处理能力有限,如何组织计划外的请求对系统...
    99+
    2024-04-02
  • 如何利用Redis和Haskell实现资源限制功能
    如何利用Redis和Haskell实现资源限制功能在现代的网络应用中,对于资源的管理和限制是非常重要的。资源限制可以确保服务器的稳定性,防止滥用和恶意行为。本文将介绍如何利用Redis和Haskell实现资源限制功能,并提供具体的代码示例。...
    99+
    2023-10-22
    redis Haskell 资源限制
  • FileZilla如何实现速度限制
    这篇文章给大家分享的是有关FileZilla如何实现速度限制的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。FileZilla速度限制速度限制功能就是当网络比较拥挤或FTP站点有特定要求的时候,对文件的上传和下载的...
    99+
    2023-06-16
  • php如何实现上传限制
    这篇文章主要为大家展示了“php如何实现上传限制”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“php如何实现上传限制”这篇文章吧。上传限制在这个脚本中,我们增加...
    99+
    2024-04-02
  • 实现PHP单用户登录限制
    实现PHP单用户登录限制,需要具体代码示例 在开发一个网站或应用时,有时候需要保证用户只能在一个设备上进行登录,避免多人共享账号的情况发生。为了实现这一功能,可以通过PHP编写代码来进...
    99+
    2024-03-05
    php 登录限制 单用户 php编写
  • Redis中如何限制操作频率
    这篇文章主要介绍Redis中如何限制操作频率,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!场景场景1留言功能限制,30秒 内只能评论 10次,超出次数不让能再评论,并提示:过于频繁场...
    99+
    2024-04-02
  • java中如何实现redis限流操作
    这篇文章给大家分享的是有关java中如何实现redis限流操作的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。具体操作如下。导入所需依赖<properties>   &nb...
    99+
    2023-06-21
  • Docker如何实现Memory资源限制
    这篇文章主要为大家展示了“Docker如何实现Memory资源限制”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Docker如何实现Memory资源限制”这篇文章吧。一:简介docker通过cg...
    99+
    2023-06-04
  • redis 限制内存使用大小的实现
    记录一次生产环境问题排查过程: 生产环境部署方式:nginx + uwsgi + flask 问题描述: 发现生产环境中之前正常运行的服务突然不可用了,查看程序日志发现部分接口访问...
    99+
    2024-04-02
  • 怎么在CSS中利用radial-gradient 实现一个优惠券样式
    怎么在CSS中利用radial-gradient 实现一个优惠券样式?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。绘制基本样式<div clas...
    99+
    2023-06-08
  • Docker如何实现CPU资源限制
    这篇文章主要介绍了Docker如何实现CPU资源限制,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一: 描述 Windows系统使用 --cpu-period int&nbs...
    99+
    2023-06-04
  • vue-router如何实现权限控制
    这篇文章主要讲解了“vue-router如何实现权限控制”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue-router如何实现权限控制”吧!在vue-router控制前端权限是常见需求:...
    99+
    2023-07-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作