Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理

目录

基于Stream的消息队列

Redis优化秒杀

登录头 

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

Redis消息队列实现异步秒杀

​编辑基于List结构模拟消息队列 

基于PuSub的消息队列

 ​编辑

  基于Stream的消息队列

Redis消息队列


基于Stream的消息队列

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第1张图片

Redis优化秒杀

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第2张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第3张图片

登录头 

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第4张图片

 Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第5张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第6张图片

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

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第7张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第8张图片


@Service
public class VoucherOrderServiceImpl extends ServiceImpl implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    private static final DefaultRedisScript SECKILL_SCRIPT;
    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }
//    阻塞队列
    private BlockingQueue orederTasks = new ArrayBlockingQueue(1024*1024);
//创建线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    @PostConstruct
    private void init(){
        //TODO 类初始化的时候执行线程池
        SECKILL_ORDER_EXECUTOR.submit(new VocherOrderHandler());

    }
    private class VocherOrderHandler implements Runnable{

        @Override
        public void run() {
            while (true) {
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = orederTasks.take();
//                    2.创建订单
                    handleVocherOrder(voucherOrder);
                }
                catch (Exception e) {
//                    e.printStackTrace();
                    log.error("处理订单异常" ,e);
                }
            }
        }
    }

    private void handleVocherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
//        Long userId = UserHolder.getUser().getId();
//        synchronized锁还是有线程问题
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
        //TODO 分布锁
        //TODO 创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//        TODO 使用工具Redisson
        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁
        //TODO
        boolean isLock = lock.tryLock();
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            log.error("不允许重复下单");
            return ;

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
//            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            proxy.createVoucherOrderTwo(voucherOrder);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    private IVoucherOrderService proxy;
    //    TODO
    @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?"库存不足":"不能重复下单");

        }
        //TODO 2.2为0, 有购买资格,把下单信息保存到阻塞队列
        long orderId = redisIdWorker.nextId("order");
        //2.3.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //2.3订单di
        voucherOrder.setId(orderId);
        //2.4用户id
//        Long userId = UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        //2.5代金卷id
        voucherOrder.setVoucherId(voucherId);
        //TODO 2.6 订单放入堵塞队列
        orederTasks.add(voucherOrder);

        //TODO 3.获取代理对象 创建全局变量其他地方才可以用
         proxy = (IVoucherOrderService)AopContext.currentProxy();

        //7.返回订单
        //TODO 保存阻塞队列

        //3.返回订单id

        return Result.ok(orderId);
    }
  /*  @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠卷
        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();
//        synchronized锁还是有线程问题
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
        //TODO 分布锁
        //TODO 创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//        TODO 使用工具Redisson
        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁
        //TODO
        boolean isLock = lock.tryLock();
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            return Result.fail("不允许重复下单");

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
//
//        }
    }
*/
  @Transactional
  public void createVoucherOrderTwo(VoucherOrder voucherOrder) {
      //TODO 6.一人一单
//      TODO 不是在主线程了,在线程池了
      Long userId = voucherOrder.getUserId();
//        userId.toString()底层代码是创建对象,所以有可能还是不一样的
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示


      //TODO 6.1查询订单
      Integer count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
      //TODO 6.2判断是否存在
      if ( count > 0 ) {
          //用户已经买过了
          log.error("用户已经买过了一次了");
          return ;
      }


      //5.扣减库存
      boolean sucess = seckillVoucherService.update()
              .setSql("stock = stock-1")//set stock = stock-1
              .gt("stock", "0")//可以解决超卖 where id ?and stock >0
              .eq("voucher_id", voucherOrder.getVoucherId()).update();//
//                .eq("stock",voucher.getStock()).update();//where id ?and stock = ?

      if ( !sucess ) {
          //扣减不足
          log.error("库存不足");
          return ;

      }
      //7.0创建订单
      save(voucherOrder);

  }
    @Transactional//加入事务
    public   Result createVoucherOrder(Long voucherId) {
        //TODO 6.一人一单
        Long userId = UserHolder.getUser().getId();
//        userId.toString()底层代码是创建对象,所以有可能还是不一样的
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示


            //TODO 6.1查询订单
            Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            //TODO 6.2判断是否存在
            if ( count > 0 ) {
                //用户已经买过了
                return Result.fail("用户已经买过了一次了");
            }


            //5.扣减库存
            boolean sucess = seckillVoucherService.update()
                    .setSql("stock = stock-1")//set stock = stock-1
                    .gt("stock", "0")//可以解决超卖 where id ?and stock >0
                    .eq("voucher_id", voucherId).update();//
//                .eq("stock",voucher.getStock()).update();//where id ?and stock = ?

            if ( !sucess ) {
                //扣减不足
                return Result.fail("库存不足");

            }


            //6.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //6.1订单di
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //6.2用户id
//        Long userId = UserHolder.getUser().getId();
            voucherOrder.setUserId(userId);
            //6.3代金卷id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //7.返回订单
            return Result.ok(orderId);

    }


}

Redis消息队列实现异步秒杀

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第9张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第10张图片基于List结构模拟消息队列 

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第11张图片 Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第12张图片

基于PuSub的消息队列

 Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第13张图片

 Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第14张图片

  基于Stream的消息队列

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第15张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第16张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第17张图片 Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第18张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第19张图片

 Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第20张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第21张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第22张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第23张图片

Redis消息队列

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第24张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第25张图片

Redis 优惠卷秒杀(二) 异步秒杀、基于Stream的消息队列处理_第26张图片


@Service
public class VoucherOrderServiceImpl extends ServiceImpl implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    private static final DefaultRedisScript SECKILL_SCRIPT;
    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }
//    TODO
//创建线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    @PostConstruct
    private void init(){
        //TODO 类初始化的时候执行线程池
        SECKILL_ORDER_EXECUTOR.submit(new VocherOrderHandler());

    }
    private class VocherOrderHandler implements Runnable{
        String querName = "stream.orders";

        @Override
        public void run() {
            while (true) {
                try {
                    //1.获取消息队列中的订单信息
//                    XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.order >
                    List> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
                            StreamOffset.create(querName, ReadOffset.lastConsumed())
                    );
                    //判断消息获取是否成功
                    if ( list == null || list.isEmpty() ) {
                        //2.1如果获取失败,说明没有消息,继续下一次循环
                        continue;

                    }
                    //3.解析消息中的订单信息
                    MapRecord record = list.get(0);
                    Map value = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    //4.如果获取成功,可以下单
                    handleVocherOrder(voucherOrder);
                    //ACK确认 SACK stream.orders g1 id
                    stringRedisTemplate.opsForStream().acknowledge(querName,"g1",record.getId());
                }
                catch (Exception e) {
//                    e.printStackTrace();
                    log.error("处理订单异常" ,e);
                    handlePendingList();
                }
            }
        }

        private void handlePendingList() {
            while (true) {
                try {
                    //1.获取pending-list中的订单信息
//                    XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.order 0
                    List> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1),
                            StreamOffset.create(querName, ReadOffset.from("0"))
                    );
                    //判断消息获取是否成功
                    if ( list == null || list.isEmpty() ) {
                        //2.1如果获取失败,说明pending-list没有异常消息,结束循环
                        continue;

                    }
                    //3.解析消息中的订单信息
                    MapRecord record = list.get(0);
                    Map value = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    //4.如果获取成功,可以下单
                    handleVocherOrder(voucherOrder);
                    //ACK确认 SACK stream.orders g1 id
                    stringRedisTemplate.opsForStream().acknowledge(querName,"g1",record.getId());
                }
                catch (Exception e) {
//                    e.printStackTrace();
                    log.error("处理pending-list订单异常" ,e);
                    try {
                        Thread.sleep(20);
                    }
                    catch (InterruptedException interruptedException) {
                        interruptedException.printStackTrace();
                    }
                }
            }

        }
    }
   /*
    //    阻塞队列
    private BlockingQueue orederTasks = new ArrayBlockingQueue(1024*1024);
    private class VocherOrderHandler implements Runnable{

        @Override
        public void run() {
            while (true) {
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = orederTasks.take();
//                    2.创建订单
                    handleVocherOrder(voucherOrder);
                }
                catch (Exception e) {
//                    e.printStackTrace();
                    log.error("处理订单异常" ,e);
                }
            }
        }
    }
    */

    private void handleVocherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
//        Long userId = UserHolder.getUser().getId();
//        synchronized锁还是有线程问题
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
        //TODO 分布锁
        //TODO 创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//        TODO 使用工具Redisson
        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁
        //TODO
        boolean isLock = lock.tryLock();
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            log.error("不允许重复下单");
            return ;

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
//            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            proxy.createVoucherOrderTwo(voucherOrder);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    private IVoucherOrderService proxy;

    //TODO
    @Override
    public Result seckillVoucher(Long voucherId) {
//        获取用户
        Long userId = UserHolder.getUser().getId();
//        获取订单id
        long orderId = redisIdWorker.nextId("order");

        //1.执行lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString(),String.valueOf(orderId));
        //2.判断结果为0
        int r = result.intValue();
        if ( r!=0 ) {
            //2.1不为0,代表没有购买资格
            return Result.fail(r==1?"库存不足":"不能重复下单");

        }
        //TODO 2.2为0, 有购买资格,把下单信息保存到阻塞队列

        //TODO 3.获取代理对象 创建全局变量其他地方才可以用
        proxy = (IVoucherOrderService)AopContext.currentProxy();

        //7.返回订单
        //TODO 保存阻塞队列

        //3.返回订单id

        return Result.ok(orderId);
    }
    //    TODO
   /* @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?"库存不足":"不能重复下单");

        }
        //TODO 2.2为0, 有购买资格,把下单信息保存到阻塞队列
        long orderId = redisIdWorker.nextId("order");
        //2.3.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //2.3订单di
        voucherOrder.setId(orderId);
        //2.4用户id
//        Long userId = UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        //2.5代金卷id
        voucherOrder.setVoucherId(voucherId);
        //TODO 2.6 订单放入堵塞队列
        orederTasks.add(voucherOrder);

        //TODO 3.获取代理对象 创建全局变量其他地方才可以用
         proxy = (IVoucherOrderService)AopContext.currentProxy();

        //7.返回订单
        //TODO 保存阻塞队列

        //3.返回订单id

        return Result.ok(orderId);
    }
    */
  /*  @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠卷
        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();
//        synchronized锁还是有线程问题
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示
        //TODO 分布锁
        //TODO 创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order" + userId, stringRedisTemplate);
//        TODO 使用工具Redisson
        RLock lock = redissonClient.getLock("lock:order" + userId);
        //获取锁
        //TODO
        boolean isLock = lock.tryLock();
        if ( !isLock ){
            //获取锁失败,返回错误或重试
            return Result.fail("不允许重复下单");

        }
        try {
            //获取代理对象(事务)事务生效
            //TODO 获取锁之后再创建事务
            IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy();
            //TODO 事务提交完再释放锁
            return proxy.createVoucherOrder(voucherId);//事务能够
            //TODO 可以避免事务没提交就释放锁的安全问题
        } finally {
            //释放锁
            lock.unlock();
        }
//
//        }
    }
*/
  @Transactional
  public void createVoucherOrderTwo(VoucherOrder voucherOrder) {
      //TODO 6.一人一单
//      TODO 不是在主线程了,在线程池了
      Long userId = voucherOrder.getUserId();
//        userId.toString()底层代码是创建对象,所以有可能还是不一样的
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示


      //TODO 6.1查询订单
      Integer count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
      //TODO 6.2判断是否存在
      if ( count > 0 ) {
          //用户已经买过了
          log.error("用户已经买过了一次了");
          return ;
      }


      //5.扣减库存
      boolean sucess = seckillVoucherService.update()
              .setSql("stock = stock-1")//set stock = stock-1
              .gt("stock", "0")//可以解决超卖 where id ?and stock >0
              .eq("voucher_id", voucherOrder.getVoucherId()).update();//
//                .eq("stock",voucher.getStock()).update();//where id ?and stock = ?

      if ( !sucess ) {
          //扣减不足
          log.error("库存不足");
          return ;

      }
      //7.0创建订单
      save(voucherOrder);

  }
    @Transactional//加入事务
    public   Result createVoucherOrder(Long voucherId) {
        //TODO 6.一人一单
        Long userId = UserHolder.getUser().getId();
//        userId.toString()底层代码是创建对象,所以有可能还是不一样的
//        synchronized(userId.toString().intern()) {//intern返回字符串的规范表示


            //TODO 6.1查询订单
            Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            //TODO 6.2判断是否存在
            if ( count > 0 ) {
                //用户已经买过了
                return Result.fail("用户已经买过了一次了");
            }


            //5.扣减库存
            boolean sucess = seckillVoucherService.update()
                    .setSql("stock = stock-1")//set stock = stock-1
                    .gt("stock", "0")//可以解决超卖 where id ?and stock >0
                    .eq("voucher_id", voucherId).update();//
//                .eq("stock",voucher.getStock()).update();//where id ?and stock = ?

            if ( !sucess ) {
                //扣减不足
                return Result.fail("库存不足");

            }


            //6.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //6.1订单di
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //6.2用户id
//        Long userId = UserHolder.getUser().getId();
            voucherOrder.setUserId(userId);
            //6.3代金卷id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //7.返回订单
            return Result.ok(orderId);

    }


}

你可能感兴趣的:(Redis,redis,数据库,缓存,spring,cloud)