黑马点评项目——通过redis实现异步秒杀

思路

未优化前,业务按照步骤依次执行,所需要的时间是所有业务时间之和,这样做的话,原本那些比如判断秒杀库存,校验一人一单的操作就会等待,直到之前的业务流程走到它时,才开始执行,这样也就会使得整个流程执行时间变长,影响体验。

 

 所以,我们优化的思路就是,将判断库存和校验一人一单的活,交给redis来做 对数据库的操作就让主线程开启一个新线程来异步帮我们进行操作。

 具体实现

1.优惠券新增时,将优惠劵的数量信息,保存到redis中,这里用String型保存即可,这样之后判断是否有库存只需要查询redis中库存数是否大于0即可。

@Override
    @Transactional
    public void addSeckillVoucher(Voucher voucher) {
        // 保存优惠券
        save(voucher);
        // 保存秒杀信息
        SeckillVoucher seckillVoucher = new SeckillVoucher();
        seckillVoucher.setVoucherId(voucher.getId());
        seckillVoucher.setStock(voucher.getStock());
        seckillVoucher.setBeginTime(voucher.getBeginTime());
        seckillVoucher.setEndTime(voucher.getEndTime());
        seckillVoucherService.save(seckillVoucher);
        // 保存秒杀库存到Redis中
        System.out.println("保存优惠卷信息到redis");
        stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());
    }

2.对于一人一单的业务,我们可以用set这个不重复集合类型,只要每次判断set中是否有用户信息,如果有就不能买,如果没有就有资格买。

对于上述两个操作,我们要保证他的原子性,所以,我们最好用lua脚本进行编写业务逻辑

(由于博主本人不太会lua所以就用java代码先实现了,这不能保证原子性)

  public boolean hasQualification(long voucherId,long userId){
//        判断库存
        String s = stringRedisTemplate.opsForValue().get(SECKILL_STOCK_KEY + voucherId);
        int number = Integer.parseInt(s);
        if(number<1){
            return false;
        }
//        判断一人一单
        Boolean buyMember = stringRedisTemplate.opsForSet().isMember("buyMember", String.valueOf(userId));
        if(!buyMember) {//有资格就先将用户信息保存到set中
//        将订单信息保存到set中
            stringRedisTemplate.opsForSet().add("buyMember", String.valueOf(userId));
        }
        return !buyMember;
    }

3.判断是否有资格购买时,主线程任务就结束了

有资格:将订单信息和用户放入阻塞队列中,拿到代理对象(由于之后事务要用到代理对象,所以在主线程中就先拿到),直接返回前端信息

没资格:直接返回信息

  @Override
    public Result seckillVoucher(Long voucherId) {
//        拿到用户id
        Long userId = UserHolder.getUser().getId();
//        判断是否有资格
        boolean hasQ = this.hasQualification(voucherId, userId);
        if(!hasQ){//没资格直接返回
            return Result.fail("用户已经下单过或优惠券已经抢完");
        }
//        有资格
//        封装订单信息
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(redisIdWorker.nextId("order"));//订单id
        voucherOrder.setUserId(userId);//用户id
        voucherOrder.setVoucherId(voucherId);//优惠券id
//        将订单信息放入阻塞队列中
        orderTasks.add(voucherOrder);
//        获取代理对象
         proxy= (IVoucherOrderService) AopContext.currentProxy();
        return Result.ok(voucherOrder.getId());
    }

4.开启子线程,并且让其轮询阻塞队列,注意子线程最好在类初始化时就执行,这时可以用到

@PostConstruct注解

之后子线程执行数据库操作即可!

  private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);//阻塞队列
    //    创建线程池,异步执行数据库操作
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
    //    让类在初始化之后立即就执行请求阻塞队列的业务
    @PostConstruct//让类在初始化时就进行执行该注解标记的方法
    private void init(){
        System.out.println("执行到这!");
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandle());//在类初始化时就提交该任务
    }
//    创建线程任务
    private class VoucherOrderHandle implements Runnable{

        @Override
        public void run() {
            while (true){
//                1.获取对列中的订单信息
                try {
                    VoucherOrder order = orderTasks.take();//拿到阻塞队列中订单信息,take:拿到并删除阻塞队列的头部,如果没有则阻塞等待
//                    创建订单
                     createOrder(order);
                } catch (InterruptedException e) {
                   log.error("阻塞队列异常");
                }
            }
        }
    private void createOrder(VoucherOrder order) {
//            拿到用户id
        Long userId = order.getUserId();
//        创建锁对象
        RLock lock = redissonClient.getLock("order:" + userId);
        boolean getLock = false;
        try {
          getLock = lock.tryLock(1L, 20L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(!getLock){
//            不成功
           log.error("获取锁异常");
           return;
        }
        try {
            proxy.createVoucherOrder(order);
        }finally {
            lock.unlock();
        }
    }
}
    @Transactional
    @Override
    public void createVoucherOrder(VoucherOrder order) {
//        扣减库存
        SeckillVoucher voucher = seckillVoucherService.getById(order.getVoucherId());
        System.out.println(voucher.getStock());
        boolean success = voucherOrderMapper.updateStock(voucher.getStock());
        if(!success){
            log.error("抢购失败,轻重新抢购!");
        }
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(redisIdWorker.nextId("order"));
        voucherOrder.setUserId(order.getUserId());
        voucherOrder.setVoucherId(order.getVoucherId());
//        将订单信息保存
        save(voucherOrder);
    }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值