问题:当前秒杀功能的执行逻辑是怎样的?
–解答:

问题:这种逻辑存在什么问题?
–解答:所有处理均由一个线程完成,性能较低。
问题:如何进行优化?
–解答:可以使用Redis完成检查库存是否充足和校验一人一单的过程,对于满足条件的线程(请求),将其放入阻塞队列中,异步的处理这些请求(生成订单,扣减库存,将订单信息存入数据库中)
问题:为什么要用阻塞队列?
–解答:因为我们想要异步的处理生成订单的请求,而不是一旦某个用户满足条件,就立即为他生成订单(这里的生成订单指的是扣减数据库中的库存)。
问题:BlockingQueue有哪些特性?
–解答:本质上是一个队列,这也是一个接口,有多个实现类,这里选择的是ArrayBlockingQueue。阻塞队列最大的特点就是,当取不到数据时,该线程会一直等待,直到队列中有数据。
问题:那这样不会影响效率吗,因为要一直等待?
–解答:由于我们在处理订单,也就是从阻塞队列中取数据时,是交给另一个线程来做的,因此对我们的主线程不会有影响,其实主线程在redis判断完后就基本上可以将秒杀结果返回给用户了。
问题:Redis中是如何工作的?
–解答:这也是我们接下来要说到的。因为要保证Redis中操作的原子性,因此我们会将这些操作以Lua脚本的方式呈现。首先我们会判断对应优惠券的库存是否充足,这里我们可以使用String,Value结构来存储数据;至于校验该用户是否购买过该优惠券,则可以使用String,Set结构来存储数据。使用Set的好处有很多,比如key中对应的value是唯一的,不会存在多个相同的value,这时只要调用set 的ismember命令就能够判断该用户是否购买过此优惠券。
因此我们现在的秒杀流程变成了这样:

实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
@Service @Slf4j public class SeckillVoucherServiceImpl extends ServiceImpl<SeckillVoucherMapper, SeckillVoucher> implements ISeckillVoucherService { @Resource private UniqueIdGenerator uniqueIdGenerator; @Resource private VoucherOrderMapper voucherOrderMapper; private static DefaultRedisScript redisScript; static{ redisScript = new DefaultRedisScript(); redisScript.setLocation(new ClassPathResource("/RedisWork.lua")); redisScript.setResultType(Long.class); } @Resource private StringRedisTemplate stringRedisTemplate; private BlockingQueue<VoucherOrder> blockingQueue = new ArrayBlockingQueue<>(1024 * 1024);
private static ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor(); private ISeckillVoucherService currentProxy;
@PostConstruct public void init(){ SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); }
private class VoucherOrderHandler implements Runnable{ public void run(){ while(true){ try { VoucherOrder take = blockingQueue.take(); handleVoucherOrder(take); } catch (Exception e) { log.error("处理订单异常",e); } } } } public Result seckillVoucher(Long id){ SeckillVoucher seckillVoucher = getById(id); if(seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())){ return Result.fail("活动未开始"); } if(seckillVoucher.getEndTime().isBefore(LocalDateTime.now())){ return Result.fail("活动已结束"); } List<String> keys = new ArrayList<>(); keys.add(RedisConstants.SECKILL_STOCK_KEY + id); keys.add(RedisConstants.SECKILL_VOUCHER_KEY + id); Long userid = UserHolder.getUser().getId(); Long flag = (Long)stringRedisTemplate.execute(redisScript, keys, userid + "");
if(flag == 1){ return Result.fail("库存不足"); } if(flag == 2){ return Result.fail("不能重复购买"); } long seckillVoucherOrderId = uniqueIdGenerator.generatorId(RedisConstants.SECKILL_VOUCHER_ID_PREFIX); VoucherOrder voucherOrder = new VoucherOrder(); voucherOrder.setId(seckillVoucherOrderId); voucherOrder.setUserId(userid); voucherOrder.setVoucherId(id); blockingQueue.add(voucherOrder); currentProxy = (ISeckillVoucherService) AopContext.currentProxy();
return Result.ok(voucherOrder); }
public void handleVoucherOrder(VoucherOrder voucherOrder){ currentProxy.createVoucherOrder(voucherOrder); }
@Transactional public void createVoucherOrder(VoucherOrder voucherOrder){ boolean success = update().setSql("stock = stock - 1").eq("voucher_id", voucherOrder.getVoucherId()).gt("stock",0).update(); voucherOrderMapper.insert(voucherOrder); } }
|