秒杀优化
VoucherOrderServiceImpl
修改下单动作,现在我们去下单时,是通过lua表达式去原子执行判断逻辑,如果判断我出来不为0 ,则要么是库存不足,要么是重复下单,返回错误信息,如果是0,则把下单的逻辑保存到队列中去,然后异步执行
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
| @Slf4j @Service public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Autowired private ISeckillVoucherService seckillVoucherService;
@Autowired private RedisIdWorker redisIdWorker;
@Autowired private StringRedisTemplate stringRedisTemplate;
@Autowired private RedissonClient redissonClient;
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static { SECKILL_SCRIPT = new DefaultRedisScript<>(); SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua")); SECKILL_SCRIPT.setResultType(Long.class); }
private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
private IVoucherOrderService proxy;
@PostConstruct private void init() { SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); }
private class VoucherOrderHandler implements Runnable {
@Override public void run() { while (true) { try { VoucherOrder voucherOrder = orderTasks.take(); handleVoucherOrder(voucherOrder); } catch (Exception e) { log.error("处理订单异常", e); } } } }
private void handleVoucherOrder(VoucherOrder voucherOrder) { Long userId = voucherOrder.getUserId();
RLock redisLock = redissonClient.getLock("lock:order:" + userId);
boolean isLock = false; try { isLock = redisLock.tryLock(1, 10, TimeUnit.SECONDS);
if (!isLock) { log.error("不允许重复下单!"); return; } proxy.createVoucherOrder(voucherOrder); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("线程被中断", e); } finally { if (isLock) { redisLock.unlock(); } } }
@Override public Result seckillVoucher(Long voucherId) { Long userId = UserHolder.getUser().getId(); Long result = stringRedisTemplate.execute( SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString() ); int r = result.intValue(); if (r != 0) { return Result.fail(r == 1 ? "库存不足" : "不能重复下单"); } VoucherOrder voucherOrder = new VoucherOrder(); Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); voucherOrder.setUserId(userId); voucherOrder.setVoucherId(voucherId); orderTasks.add(voucherOrder); proxy = (IVoucherOrderService) AopContext.currentProxy(); return Result.ok(orderId); }
@Transactional public void createVoucherOrder(VoucherOrder voucherOrder) { Long userId = voucherOrder.getUserId(); int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count(); if (count > 0) { log.error("用户已经购买过了"); return; }
boolean success = seckillVoucherService.update() .setSql("stock = stock - 1") .eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0) .update(); if (!success) { log.error("库存不足"); return; } save(voucherOrder);
} }
|
小总结:
秒杀业务的优化思路是什么?
- 先利用Redis完成库存余量、一人一单判断,完成抢单业务
- 再将下单业务放入阻塞队列,利用独立线程异步下单
- 基于阻塞队列的异步秒杀存在哪些问题?