黑马点评--Redis优化秒杀

Redis优化秒杀

异步秒杀思路:
黑马点评--Redis优化秒杀_第1张图片

思路,在多线程同时秒杀时,由于判断秒杀库存和校验一人一单用时短,而减库存和创建订单时间耗时长。

所以采用同步思路就浪费时间。如果采用异步思路就能:在redis中判断秒杀库存和校验一人一单,在tomcat中读取队列中的信息对数据库进行操作。在redis返回结果,Tomcat判断生成id并返回。大大提高用户体验。

Redis的操作通过Lua脚本保证原子性

黑马点评--Redis优化秒杀_第2张图片

改进秒杀业务,提高并发性能:

需求:

新增秒杀优惠券的同时,将优惠券保存到Redis中

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中
        stringRedisTemplate.opsForValue().set( SECKILL_STOCK_KEY + voucher.getId(),voucher.getStock().toString());
    }

基于Lua脚本,判断秒杀库存,一人一单,决定用户是否抢购成功,

如果抢购成功,将优惠劵id和用户id封装后存入阻塞队列

**基于Redis完成秒杀资格判断:**
 //执行lua脚本
    private static final DefaultRedisScript<Long> SECKILL_SCRIPT ;
    static {
        SECKILL_SCRIPT =new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }
    /**
     * 异步基于lua脚本实现抢购
     * @param voucherId
     * @return
     */
    @Override
    public Result seckillVoucher(Long voucherId) {
        //获取用户
        Long userId = UserHolder.getUser().getId();
        //1.执行lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(),
                userId.toString()
        );
        //2.判断结果是为0
        int r = result.intValue();
        if (r !=0 ){
            //2.1.不为0,代表没有购买资格
            return  Result.fail(r==1 ? "库存不足":"不能重复下单");
        }
        //2.2.为0,有购买资格,把下单信息保存到阻塞队列
        long orderId = redisIdWorker.nextId("order");
        //todo 保存阻塞队列
        //3.返回订单id
        return Result.ok(orderId);
    }

seckill.lua文件

--1.参数列表
--1.1.优惠券id
local voucherId = ARGV[1]
--1.2.用户id
local userId = ARGV[2]

-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId

-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get',stockKey)) <= 0 ) then
    --3.2.库存不足,返回1
    return 1
end
-- 3.2.判断用户是否下单 sismember orderKey userId
if(redis.call('sismember', orderKey,userId) ==1 ) then
    --3.3.存在, 说明是重复下单,返回2
    return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby',stockKey,-1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd',orderKey,userId)
return 0

开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能

 //阻塞队列
    private BlockingQueue<VoucherOrder> orderTasks =new ArrayBlockingQueue<>(1024*1024);

    //线程池
    private static final ExecutorService seckill_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    //当前类初始化完了就执行init方法
    @PostConstruct
    private void init(){
        seckill_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }
    //线程任务
    private  class  VoucherOrderHandler implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    //2.创建订单
                    handleVoucherOrder(voucherOrder);
                } catch (Exception e) {
                    log.error("处理订单异常",e);
                }
            }
        }
    }
    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        //1.获取用户
        Long userId =voucherOrder.getUserId();
        //2.创建锁对象
        RLock lock = redissonClient.getLock("lock:order:"+userId);
        //3.获取锁
        boolean isLock =lock.tryLock();
        //4.判断是否获取锁成功
        if (!isLock){
            //获取锁失败,返回错误或重试
            log.error("不允许重复下单");
        }
        try {
            proxy.createVoucherOrderYiBu(voucherOrder);
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    public void createVoucherOrderYiBu(VoucherOrder voucherOrder){
        //6.一个人一单
        Long userId = voucherOrder.getUserId();
        //6.1查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
        //6.2判断是否存在
        if (count > 0) {
            //用户以及购买过
            log.error("用户已经购买过一次");
            return;
        }
        //7.扣减库存
        boolean success = iSeckillVoucherService.update()
                .setSql("stock =stock -1")
                .eq("voucher_id", voucherOrder.getVoucherId())
                .gt("stock", 0).update();
        if (!success) {
            //扣减失败
            log.error("库存不足");
            return ;
        }
        save(voucherOrder);
    }

你可能感兴趣的:(Java,redis,lua,数据库)