仿黑马点评-redis整合【四 优惠卷秒杀(上) 】

前言
作者简介:我是笑霸final,一名热爱技术的在校学生。
个人主页:个人主页1 || 笑霸final的主页2
系列专栏:《项目专栏》
如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步
如果感觉博主的文章还不错的话,点赞 + 关注 + 收藏

在这里插入图片描述

目录

  • 介绍
  • 全局唯一ID
  • 添加优惠卷
    • 新增优惠卷json数据
  • 实现优惠卷秒杀下单(基本下单)
  • 实现优惠卷秒杀下单(超卖问题)
    • 乐观锁实现方案
  • 实现优惠卷秒杀下单(一人一单)

介绍

仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第1张图片

全局唯一ID

我们使用全局id生成器
仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第2张图片
我们能不能用redis来完成这个任务?
仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第3张图片
代码

// 1. 生成时间戳//2.生成序列号//3.拼接并返回
获取时间戳

  public static void main(String[] args) {
        //获取2022年 1月 1号 0点的秒数
        LocalDateTime time = LocalDateTime.of(2022, 1, 1, 0, 0);
        long second = time.toEpochSecond(ZoneOffset.UTC);
        log.info(Long.toString(second));
    }
public Long nexId(String keyPrefix){
       // 1. 生成时间戳 31位数字 单位秒
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);//当前秒数
        Long timestamp=nowSecond- BEG_TIMESTAMP;

        //2.生成序列号
        //2.1获取当前日期
        String yyyyMMdd = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        //2.2自增长
        Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + yyyyMMdd);
        //3.拼接并返回

        return timestamp<<32 | count;
    }

仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第4张图片

添加优惠卷

背景:每个商代都可以发布优惠卷,评价卷都可以任意购买,优惠卷需要秒杀抢购!!

数据库

  • 平价卷仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第5张图片
  • 秒杀卷
    仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第6张图片

新增优惠卷json数据

url地址:http://localhost:8081/voucher/seckill

{
    "shopId":1,
    "title":"100元代金卷",
    "subTitle":"周一到周五可用",
    "rules":"测试",
    "payValue":8000,
    "actualValue":10000,
    "type":1,
    "stock":100,
    "beginTime":"2022-01-25T10:09:17",
    "endTime":"2023-01-25T10:09:17",
    
}

实现优惠卷秒杀下单(基本下单)

仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第7张图片

一般流程

仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第8张图片

  • 判断秒杀是否开始,或者结束
  • 库存是否充足

代码
仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第9张图片

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    @Transactional//两张表 加上事务
    public Result seckillVoucher(Long voucherId) {

        //1.查询id
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //没有开始
            return Result.fail("时间还没开始");
        }
        //3.判断是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //已经结束
            return Result.fail("已经结束");
        }

        //4.判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("库存不足");
        }
        //5.扣减库存
        boolean success= seckillVoucherService
                .update()
                .setSql("stock=stock-1")
                .eq("voucher_id",voucherId).update();
        if (!success){
            //库存不足
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1订单id 用id全局唯一生成器
        Long orderId = redisIdWorker.nexId("order");
        voucherOrder.setId(orderId);
        //6.2用户id
        Long userId = UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        //6.3代金卷id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //7.返回id
        return Result.ok(orderId);
    }
}

实现优惠卷秒杀下单(超卖问题)

解决超卖问题可以加锁:

  • 乐观锁:认为线程安全问题不一定会发生,因此不加锁,只是在更新数据时去判断有没有其它线程对数据做了修改。如果没有修改则认为是安全的,自己才更新数据。如果已经被其它线程修改说明发生了安全问题,此时可以重试或异常。
  • 悲观锁:认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。例如Synchronized、Lock都属于悲观锁

仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第10张图片

乐观锁实现方案

1.版本号法(运用最广泛的):每次更新数据库的时候按照版本查询,并且要更新版本。

2.CAS
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第11张图片

CAS法代码:

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override
    @Transactional//两张表 加上事务
    public Result seckillVoucher(Long voucherId) {

        //1.查询id
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //没有开始
            return Result.fail("时间还没开始");
        }
        //3.判断是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //已经结束
            return Result.fail("已经结束");
        }

        //4.判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("库存不足");
        }
        //5.扣减库存 乐观锁 在这一刻去判断更新时和查询到的库存是否一致
            //        boolean success= seckillVoucherService
            //                .update()
            //                .setSql("stock=stock-1")
            //                .eq("voucher_id",voucherId).update();
        boolean success= seckillVoucherService
                .update()
                .setSql("stock=stock-1")
                .eq("voucher_id",voucherId)
                .eq("stock",voucher.getStock())
                .update();
        if (!success){
            //库存不足
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1订单id 用id全局唯一生成器
        Long orderId = redisIdWorker.nexId("order");
        voucherOrder.setId(orderId);
        //6.2用户id
        Long userId = UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        //6.3代金卷id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //7.返回id
        return Result.ok(orderId);
    }
}

弊端:成功率太低:
改进一下
判断库存和0的大小
仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第12张图片

实现优惠卷秒杀下单(一人一单)

仿黑马点评-redis整合【四 优惠卷秒杀(上) 】_第13张图片

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Override

    public Result seckillVoucher(Long voucherId) {

        //1.查询id
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //没有开始
            return Result.fail("时间还没开始");
        }
        //3.判断是否结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //已经结束
            return Result.fail("已经结束");
        }

        //4.判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("库存不足");
        }

        Long userId = UserHolder.getUser().getId();
        //intern()在常量池里先去找一样的地址返回
        synchronized(userId.toString().intern()) {
            VoucherOrderServiceImpl proxy 
                    =(VoucherOrderServiceImpl)AopContext.currentProxy();//代理对象
            //因为没有加事务 事务用的代理对象 可能存在事务失效
            //没有用代理对象默认是 this.creteVoucherOrder(voucherId)
            return proxy.creteVoucherOrder(voucherId);
        }
    }

    @Transactional//两张表 加上事务
    public  Result creteVoucherOrder(Long voucherId) {
        //5一人一单

        Long userId = UserHolder.getUser().getId();
    
            //5.1查询订单
            Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();

            //5.2查询是否存在
            if (count > 0) {
                //存在就不能下单了
                return Result.fail("你已经购买过了,只能买一次哟");
            }
            //6.扣减库存 乐观锁 在这一刻去判断更新时和查询到的库存是否一致
            //        boolean success= seckillVoucherService
            //                .update()
            //                .setSql("stock=stock-1")
            //                .eq("voucher_id",voucherId).update();
            boolean success = seckillVoucherService
                    .update()
                    .setSql("stock=stock-1")
                    .eq("voucher_id", voucherId)
//                .eq("stock",voucher.getStock())
                    .gt("stock", 0)//判断 stock>0可行
                    .update();
            if (!success) {
                //库存不足
                return Result.fail("库存不足");
            }
            //7.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //7.1订单id 用id全局唯一生成器
            Long orderId = redisIdWorker.nexId("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整合【四 优惠卷秒杀(上) 】_第14张图片
需要加上

 		<dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
        dependency>

同时启动类也加上@EnableAspectJAutoProxy(exposeProxy = true)//暴露代理对象

你可能感兴趣的:(项目,redis,数据库,nosql,java,spring,boot)