网站未备案,推广公司如何赚钱,wordpress主题lovephoto3.0,南通 网络 公司网站通过异步使用消息队列优化秒杀 同步秒杀流程异步优化秒杀异步秒杀流程基于lua脚本保证Redis操作原子性代码实现阻塞队列的缺点 同步秒杀流程 public Result seckillVoucher(Long voucherId) throws InterruptedException {SeckillVoucher seckillVoucher iSeckillVoucherServi… 通过异步使用消息队列优化秒杀 同步秒杀流程异步优化秒杀异步秒杀流程基于lua脚本保证Redis操作原子性代码实现阻塞队列的缺点 同步秒杀流程 public Result seckillVoucher(Long voucherId) throws InterruptedException {SeckillVoucher seckillVoucher iSeckillVoucherService.getById(voucherId);LocalDateTime beginTime seckillVoucher.getBeginTime();if (LocalDateTime.now().isBefore(beginTime)) {return Result.fail(秒杀还未开始);}LocalDateTime endTime seckillVoucher.getEndTime();if (LocalDateTime.now().isAfter(endTime)) {return Result.fail(秒杀已经结束);}
// 查看库存Integer stock seckillVoucher.getStock();if (stock 1) {return Result.fail(库存不足);}Long userId UserHolder.getUser().getId();
// redis集群实现RLock lock redissonClient.getLock(order: userId);boolean isLock lock.tryLock(1L, TimeUnit.SECONDS);if (!isLock) {return Result.fail(你已经购买优惠卷);}try {IVoucherOrderService proxy (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId, userId);} finally {
// redisLock.releaseLock();lock.unlock();}}Transactionalpublic Result createVoucherOrder(Long voucherId, Long userId) {Integer count query().eq(user_id, userId).eq(voucher_id, voucherId).count();if (count 0) {return Result.fail(已经购买优惠卷);}
// 基于乐观锁避免超卖问题在乐观锁的基础上做出了改进当要修改库存时判断库存是否大于0但是也给数据库带来了巨大的压力boolean success iSeckillVoucherService.update().setSql(stockstock-1).eq(voucher_id, voucherId).gt(stock, 0).update();if (!success) {return Result.fail(库存不足);}VoucherOrder voucherOrder new VoucherOrder();long orderId redisWorker.getGlobalId(order);voucherOrder.setId(orderId);voucherOrder.setVoucherId(voucherId);voucherOrder.setUserId(userId);save(voucherOrder);return Result.ok(orderId);}以上是我们同步秒杀的流程首先会从数据库中查询优惠卷检查库存和是否在秒杀阶段然后使用redisson基于Redis实现分布式锁解决一人一单问题如果都通过后就会修改库存并且生成订单整体的流程如下 在这个流程中我们发现整个步骤都是同步都是交给主线程来执行负责检验库存保证一人一单然后来修改数据库数据我们是否可以再请一个人来优化这个流程呢比如A来负责检验检验成功后交给B来完成数据库的修改这样是不是能够优化秒杀的业务
异步优化秒杀
我们可以这样做让主线程来负责判断这个人能不能抢杀如果抢杀成功交给另一个人来负责修改数据库主线程直接返回订单id。 那怎样让主线程快速的库存和一人一单的判断呢Redis 我们可以将库存保存在Redis的字符串类型中Redis的性能高于直接查询数据库的性能而一人一单的校验我们可以使用Redis的set集合校验是否一人一单就看Redis的set集合中是否含有用户id而当主线程通过校验后让Redis中的库存-1并且将用户id添加到set集合中。 而我们怎样让修改数据库的任务交给另一个人来处理呢使用阻塞队列或者消息队列为了实现简单暂且使用阻塞队列我们将要修改数据库的任务放入阻塞队列中创建一个线程池让线程池来负责执行修改数据库的任务。
异步秒杀流程 从流程中看出我们主线程只负责进行库存校验和确保一人一单后生成订单添加到阻塞队列或消息队列中就直接返回大大提高了业务的性能。
基于lua脚本保证Redis操作原子性
如果我们使用以上的操作来实现代码会发生并发安全问题因为校验Redis操作和更新Redis操作不具有原子性就可能发生问题例如一个线程通过校验Redis库存等还没有进行修改另一个线程直接进入比较没有修改的库存就发生了线程安全问题我们需要保证这些操作具有原子性
代码实现
lua脚本
--优惠卷id
local voucherId ARGV[1];
--用户id
local userId ARGV[2];
--库存键
local stockKey voucher:stock: voucherId;
--下过订单的用户键
local orderKey voucher:order: voucherId;
--查看卷库存是否足够
if (tonumber(redis.call(get, stockKey)) 0) then
return 1;
end
if (redis.call(sismember,orderKey,userId)1) then
return 2
end
--扣库存
redis.call(incrby,stockKey,-1);
--添加用户id
redis.call(sadd,orderKey,userId)
return 0/*** 异步秒杀*/Overridepublic Result seckillVoucher(Long voucherId) throws InterruptedException {Long userId UserHolder.getUser().getId();Long result redisTemplate.execute(SECkILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString());int re result.intValue();if (re ! 0) {return re 1 ? Result.fail(库存不足) : Result.fail(不能重复下单);}long orderId redisWorker.getGlobalId(voucher:order);VoucherOrder voucherOrder new VoucherOrder();voucherOrder.setVoucherId(voucherId);voucherOrder.setId(orderId);voucherOrder.setUserId(userId);VOUCHER_PROXY (IVoucherOrderService) AopContext.currentProxy();BLOCKING_QUEUE.add(voucherOrder);return Result.ok(orderId);}
// 初始化lua脚本环境private static final DefaultRedisScriptLong SECkILL_SCRIPT;static {SECkILL_SCRIPT new DefaultRedisScript();SECkILL_SCRIPT.setLocation(new ClassPathResource(seckill.lua));SECkILL_SCRIPT.setResultType(Long.class);}// 在类初始化完之后执行PostConstructprivate void init() {EXECUTOR_SERVICE.execute(new VoucherHandler());}
// 阻塞队列获取任务处理private class VoucherHandler implements Runnable {Overridepublic void run() {while (true){try {
// 从阻塞队列中获取订单信息VoucherOrder voucherOrder BLOCKING_QUEUE.take();
// 更新数据库库存以及创建优惠卷订单VOUCHER_PROXY.createVoucherOrder(voucherOrder.getVoucherId(), voucherOrder.getUserId());} catch (InterruptedException e) {e.printStackTrace();}}}}Transactionalpublic Result createVoucherOrder(Long voucherId, Long userId) {Integer count query().eq(user_id, userId).eq(voucher_id, voucherId).count();if (count 0) {return Result.fail(已经购买优惠卷);}
// 基于乐观锁避免超卖问题在乐观锁的基础上做出了改进当要修改库存时判断库存是否大于0但是也给数据库带来了巨大的压力boolean success iSeckillVoucherService.update().setSql(stockstock-1).eq(voucher_id, voucherId).gt(stock, 0).update();if (!success) {return Result.fail(库存不足);}VoucherOrder voucherOrder new VoucherOrder();long orderId redisWorker.getGlobalId(order);voucherOrder.setId(orderId);voucherOrder.setVoucherId(voucherId);voucherOrder.setUserId(userId);save(voucherOrder);return Result.ok(orderId);}阻塞队列的缺点
在我们进行数据库的修改任务时我们使用了阻塞队列来实现在实际的业务中我们需要使用一些消息队列来代替阻塞队列阻塞队列使用的是JVM中的内存当消息过多时会造成JVM内存爆满并且功能不够强大我们可以使用Redis的Stream或者MQ来实现消息队列来代替阻塞队列