image.png
image.png

基于Redis完成秒杀资格判断

改进秒杀业务,需求:

  1. 新增秒杀优惠券同时,将优惠券信息保存到Redis中
  2. 基于Lua脚本,判断秒杀库存、一人一单,决定用户是否抢购成功
  3. 如果抢购成功,将优惠券id和用户id封装后存入阻塞队列
  4. 开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能 ```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

  1. <a name="zNuZS"></a>
  2. ## 基于阻塞队列实现异步下单
  3. ```lua
  4. private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
  5. static {
  6. SECKILL_SCRIPT = new DefaultRedisScript<>();
  7. SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
  8. SECKILL_SCRIPT.setResultType(Long.class);
  9. }
  10. private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
  11. private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
  12. @PostConstruct
  13. private void init(){
  14. SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
  15. }
  16. private class VoucherOrderHandler implements Runnable{
  17. @Override
  18. public void run() {
  19. while(true){
  20. try {
  21. VoucherOrder voucherOrder = orderTasks.take();
  22. createVoucherOrder(voucherOrder);
  23. }catch(Exception e){
  24. log.error("处理订单异常", e);
  25. }
  26. }
  27. }
  28. }
  29. @Override
  30. public Result seckillVoucher(Long voucherId) {
  31. Long userId = UserHolder.getUser().getId();
  32. long orderId = redisIdWorker.nextId("order");
  33. // 1.执行lua脚本
  34. Long result = stringRedisTemplate.execute(
  35. SECKILL_SCRIPT,
  36. Collections.emptyList(),
  37. voucherId.toString(), userId.toString(), String.valueOf(orderId)
  38. );
  39. int r = result.intValue();
  40. // 2.判断结果是否为0
  41. if (r != 0) {
  42. // 2.1.不为0 ,代表没有购买资格
  43. return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
  44. }
  45. //2.2 为0,有购买资格,把下单信息保存到阻塞队列
  46. VoucherOrder voucherOrder = new VoucherOrder();
  47. //2.3 订单id
  48. voucherOrder.setId(orderId);
  49. //2.4 用户id
  50. voucherOrder.setUserId(userId);
  51. //2.5代金券id
  52. voucherOrder.setVoucherId(voucherId);
  53. //2.6放入阻塞队列
  54. orderTasks.add(voucherOrder);
  55. return Result.ok(orderId);
  56. }
  57. private void createVoucherOrder(VoucherOrder voucherOrder) {
  58. Long userId = voucherOrder.getUserId();
  59. Long voucherId = voucherOrder.getVoucherId();
  60. // 创建锁对象
  61. RLock redisLock = redissonClient.getLock("lock:order:" + userId);
  62. // 尝试获取锁
  63. boolean isLock = redisLock.tryLock();
  64. // 判断
  65. if (!isLock) {
  66. // 获取锁失败,直接返回失败或者重试
  67. log.error("不允许重复下单!");
  68. return;
  69. }
  70. try {
  71. // 5.1.查询订单
  72. int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
  73. // 5.2.判断是否存在
  74. if (count > 0) {
  75. // 用户已经购买过了
  76. log.error("不允许重复下单!");
  77. return;
  78. }
  79. // 6.扣减库存
  80. boolean success = seckillVoucherService.update()
  81. .setSql("stock = stock - 1") // set stock = stock - 1
  82. .eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
  83. .update();
  84. if (!success) {
  85. // 扣减失败
  86. log.error("库存不足!");
  87. return;
  88. }
  89. // 7.创建订单
  90. save(voucherOrder);
  91. } finally {
  92. // 释放锁
  93. redisLock.unlock();
  94. }
  95. }