应用场景:如双11的时候大量订单请求,此时如果每个请求都交给Mysql来处理会给数据库带来具大的访问压力;此时就用到消息队列将用户请求的数据存到队列中,队列中的数据在流量低的时候再慢慢加入到数据库中。
解决问题:异步解耦:将下单请求快速写入消息队列,异步处理库存扣减和订单生成,避免阻塞主线程,提升系统响应速度;
流量削峰:应对秒杀等高并发场景,通过消息队列暂存请求,防止数据库瞬间承压;
技术:Redis List 队列。使用 LPUSH 写入消息,RPOP/BRPOP 消费消息,实现生产者 - 消费者模型;
通过阻塞读取(BRPOP)减少轮询开销;
Redis命令:GET key:获取库存值(如 seckill:stock:{voucherId})。
INCRBY key increment:原子性扣减库存(如 INCRBY stock_key -1)。
消息队列:就是下单之后,利用redis去进行校验下单条件,再通过队列把消息存入消息队列中,然后再启动一个线程去消费这个消息,完成解耦,同时也加快我们的响应速度
List:不受限于JVM内存上限、数据安全性有保证、可以满足消息有序性、无法避免消息丢失、只支持单消费者。
PubSub:采用发布订阅模型,支持多生产、多消费、不支持数据持久化、无法避免消息丢失、消息堆积有上限,超出时数据丢失
Stream:消息可回溯、一个消息可以被多个消费者读取、可以阻塞读取、有消息漏读的风险
// Rul脚本,判断用户是否有购买资格 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 ArrayBlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024); // 符合购买条件则将其信息加入消息队列中 @Override public Result seckillVoucher(Long voucherId) { Long id = UserHolder.getUser().getId(); // 执行lua脚本 Long execute = stringRedisTemplate.execute( SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), id.toString() ); int r = execute.intValue(); // 判断结果是否为0 if(r != 0){ // 不为0, 没有下单资格 return Result.fail(r == 1 ? "库存不足" : "不能重复下单"); } //为0,把下单信息保存到阻塞队列 long order = redisIdWorker.nexId("order"); VoucherOrder voucherOrder = new VoucherOrder(); voucherOrder.setId(order); voucherOrder.setUserId(id); voucherOrder.setVoucherId(voucherId); orderTasks.add(voucherOrder); // 获取代理对象(事务) proxy = (IVoucherOrderService) AopContext.currentProxy(); // 返回订单id return Result.ok(order); }
---优惠券id local voucher = ARGV[1] --本次需要购买优惠券的用户id local user = ARGV[2] -- 库存,保存有多少优惠券 local stock_key = "seckill:stock:" .. voucher --订单key, 保存购买优惠券的用户id集合 local order_key = "seckill:order:" .. voucher -- 判断库存是否为充足 if(tonumber(redis.call("get",stock_key)) <= 0) then -- 库存不足 return 1 end -- 判断用户是否已经购买过 if(redis.call("sismember",order_key,user) == 1) then -- 用户已经购买过 return 2 end -- 扣减库存 redis.call("incrby",stock_key,-1) -- 将用户id保存到集合中 redis.call('sadd', order_key, user) return 0