黑马点评实战篇|第六篇:秒杀优化
秒杀优化思路先看原本的思路1、查询优惠卷2、判断秒杀库存是否足够3、查询订单4、校验是否是一人一单5、扣减库存6、创建订单这6大步骤会在一个线程里串行执行大大拖慢响应速度所以需要程序异步执行优化思路只需要把耗时较短的流程判断秒杀库存是否足够和校验一人一单放入redis中只要执行完了这两个流程就是一定可以下单的那么就可以先给前端返回成功然后在后台开一个线程扣减库存和创建订单就可以在后台异步执行这样就可以加快执行速度难点1.怎么在redis里做到库存判断、校验一人一单2.我们怎么知道最后下单是否成功为了解决此问题我们会在redis执行完操作后给前端返回数据并且存储到异步queue中这样就可以后续根据id查询订单是否完成注意异步queue中是存储的json格式的订单核心数据整体思路在客户端发起请求后redis需要根据id来查询库存是否充足是否是一人一单如果redis返回的是0的话则可以下单(这里需要同步执行那么需要使用lua脚本)判断redis返回为0后把优惠券id用户id订单id存入阻塞队列(异步queue)然后给前端返回订单id表示成功并在后台异步执行后续创建订单的流程代码实现lua脚本模块-- 1.参数列表 -- 1.1.优惠券id local voucherId ARGV[1] -- 1.2.用户id local userId ARGV[2] -- 1.3.订单id local orderId ARGV[3] -- 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) -- 3.6.发送消息到队列中 XADD stream.orders * k1 v1 k2 v2 ... redis.call(xadd, stream.orders, *, userId, userId, voucherId, voucherId, id, orderId) return 0执行lua脚本Override public Result seckillVoucher(Long voucherId) { //获取用户 Long userId UserHolder.getUser().getId(); long orderId redisIdWorker.nextId(order); // 1.执行lua脚本 Long result stringRedisTemplate.execute( SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString(), String.valueOf(orderId) ); int r result.intValue(); // 2.判断结果是否为0 if (r ! 0) { // 2.1.不为0 代表没有购买资格 return Result.fail(r 1 ? 库存不足 : 不能重复下单); } //TODO 保存阻塞队列 // 3.返回订单id return Result.ok(orderId); }秒杀优化-基于阻塞队列实现秒杀优化修改下单动作现在我们去下单时是通过lua表达式去原子执行判断逻辑如果判断我出来不为0 则要么是库存不足要么是重复下单返回错误信息如果是0则把下单的逻辑保存到队列中去然后异步执行//异步处理线程池 private static final ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor(); //在类初始化之后执行因为当这个类初始化好了之后随时都是有可能要执行的 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 redisLock redissonClient.getLock(lock:order: userId); // 3.尝试获取锁 boolean isLock redisLock.lock(); // 4.判断是否获得锁成功 if (!isLock) { // 获取锁失败直接返回失败或者重试 log.error(不允许重复下单); return; } try { //注意由于是spring的事务是放在threadLocal中此时的是多线程事务会失效 proxy.createVoucherOrder(voucherOrder); } finally { // 释放锁 redisLock.unlock(); } } //a private BlockingQueueVoucherOrder orderTasks new ArrayBlockingQueue(1024 * 1024); Override public Result seckillVoucher(Long voucherId) { Long userId UserHolder.getUser().getId(); long orderId redisIdWorker.nextId(order); // 1.执行lua脚本 Long result stringRedisTemplate.execute( SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString(), String.valueOf(orderId) ); int r result.intValue(); // 2.判断结果是否为0 if (r ! 0) { // 2.1.不为0 代表没有购买资格 return Result.fail(r 1 ? 库存不足 : 不能重复下单); } VoucherOrder voucherOrder new VoucherOrder(); // 2.3.订单id long orderId redisIdWorker.nextId(order); voucherOrder.setId(orderId); // 2.4.用户id voucherOrder.setUserId(userId); // 2.5.代金券id voucherOrder.setVoucherId(voucherId); // 2.6.放入阻塞队列 orderTasks.add(voucherOrder); //3.获取代理对象 proxy (IVoucherOrderService)AopContext.currentProxy(); //4.返回订单id return Result.ok(orderId); } Transactional public void createVoucherOrder(VoucherOrder voucherOrder) { Long userId voucherOrder.getUserId(); // 5.1.查询订单 int count query().eq(user_id, userId).eq(voucher_id, voucherOrder.getVoucherId()).count(); // 5.2.判断是否存在 if (count 0) { // 用户已经购买过了 log.error(用户已经购买过了); return ; } // 6.扣减库存 boolean success seckillVoucherService.update() .setSql(stock stock - 1) // set stock stock - 1 .eq(voucher_id, voucherOrder.getVoucherId()).gt(stock, 0) // where id ? and stock 0 .update(); if (!success) { // 扣减失败 log.error(库存不足); return ; } save(voucherOrder); }思路总结先利用Redis完成库存余量、一人一单判断完成抢单业务再将下单业务放入阻塞队列利用独立线程异步下单基于阻塞队列的异步秒杀存在哪些问题内存限制问题数据安全问题
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2420210.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!