赞
踩
异步秒杀思路:
思路,在多线程同时秒杀时,由于判断秒杀库存和校验一人一单用时短,而减库存和创建订单时间耗时长。
所以采用同步思路就浪费时间。如果采用异步思路就能:在redis中判断秒杀库存和校验一人一单,在tomcat中读取队列中的信息对数据库进行操作。在redis返回结果,Tomcat判断生成id并返回。大大提高用户体验。
Redis的操作通过Lua脚本保证原子性
改进秒杀业务,提高并发性能:
需求:
新增秒杀优惠券的同时,将优惠券保存到Redis中
public void addSeckillVoucher(Voucher voucher) {
// 保存优惠券
save(voucher);
// 保存秒杀信息
SeckillVoucher seckillVoucher = new SeckillVoucher();
seckillVoucher.setVoucherId(voucher.getId());
seckillVoucher.setStock(voucher.getStock());
seckillVoucher.setBeginTime(voucher.getBeginTime());
seckillVoucher.setEndTime(voucher.getEndTime());
seckillVoucherService.save(seckillVoucher);
//保存秒杀库存到Redis中
stringRedisTemplate.opsForValue().set( SECKILL_STOCK_KEY + voucher.getId(),voucher.getStock().toString());
}
基于Lua脚本,判断秒杀库存,一人一单,决定用户是否抢购成功,
如果抢购成功,将优惠劵id和用户id封装后存入阻塞队列
**基于Redis完成秒杀资格判断:** //执行lua脚本 private static final DefaultRedisScript<Long> SECKILL_SCRIPT ; static { SECKILL_SCRIPT =new DefaultRedisScript<>(); SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua")); SECKILL_SCRIPT.setResultType(Long.class); } /** * 异步基于lua脚本实现抢购 * @param voucherId * @return */ @Override public Result seckillVoucher(Long voucherId) { //获取用户 Long userId = UserHolder.getUser().getId(); //1.执行lua脚本 Long result = stringRedisTemplate.execute( SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString() ); //2.判断结果是为0 int r = result.intValue(); if (r !=0 ){ //2.1.不为0,代表没有购买资格 return Result.fail(r==1 ? "库存不足":"不能重复下单"); } //2.2.为0,有购买资格,把下单信息保存到阻塞队列 long orderId = redisIdWorker.nextId("order"); //todo 保存阻塞队列 //3.返回订单id return Result.ok(orderId); }
seckill.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
开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能
//阻塞队列 private BlockingQueue<VoucherOrder> orderTasks =new ArrayBlockingQueue<>(1024*1024); //线程池 private static final ExecutorService seckill_ORDER_EXECUTOR = Executors.newSingleThreadExecutor(); //当前类初始化完了就执行init方法 @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 lock = redissonClient.getLock("lock:order:"+userId); //3.获取锁 boolean isLock =lock.tryLock(); //4.判断是否获取锁成功 if (!isLock){ //获取锁失败,返回错误或重试 log.error("不允许重复下单"); } try { proxy.createVoucherOrderYiBu(voucherOrder); }finally { //释放锁 lock.unlock(); } } public void createVoucherOrderYiBu(VoucherOrder voucherOrder){ //6.一个人一单 Long userId = voucherOrder.getUserId(); //6.1查询订单 int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count(); //6.2判断是否存在 if (count > 0) { //用户以及购买过 log.error("用户已经购买过一次"); return; } //7.扣减库存 boolean success = iSeckillVoucherService.update() .setSql("stock =stock -1") .eq("voucher_id", voucherOrder.getVoucherId()) .gt("stock", 0).update(); if (!success) { //扣减失败 log.error("库存不足"); return ; } save(voucherOrder); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。