做网站要ftp信息吗,医疗器械生产质量管理规范,wordpress FTP媒体库,网上注册公司申请入口目录
为什么需要异步秒杀
异步优化的核心逻辑是什么#xff1f;
阻塞队列的特点是什么#xff1f; Lua脚本在这里的作用是什么#xff1f; 异步调用创建订单的具体逻辑是什么#xff1f;
为什么要用代理对象proxy调用createVoucherOrder方法#xff1f; 对于代码的详细…目录
为什么需要异步秒杀
异步优化的核心逻辑是什么
阻塞队列的特点是什么 Lua脚本在这里的作用是什么 异步调用创建订单的具体逻辑是什么
为什么要用代理对象proxy调用createVoucherOrder方法 对于代码的详细解释
SECKILL_ORDER_EXECUTOR 是什么
PostConstruct 是什么
VoucherOrderHandler 是什么
VoucherOrderHandler 调用的handleVoucherOrder
数据库操作的注意点有哪些
seckillVoucher 方法 单线程线程池、阻塞队列、seckillVoucher 和 VoucherOrderHandler 的协作过程总结
方法调用流程总览
方法逻辑一览表
完整代码 在秒杀场景中我们可以将库存存入 Redis并通过 Lua 脚本来判断用户是否有秒杀资格同时实现一人一单的限制。由于 Redis 的单线程特性和 Lua 脚本的原子性保障能够避免多个线程交叉执行 Redis 命令导致的并发问题。同时使用阻塞队列将订单请求进行缓冲当线程尝试从队列中获取订单时如果队列为空线程会被阻塞直到有新订单加入队列线程才会被唤醒并处理订单从而实现高效的生产者-消费者模型。
为什么需要异步秒杀
1. 防止数据库压力过载
异步秒杀通过将订单请求写入阻塞队列削峰填谷避免将瞬时高并发请求直接传递到数据库。消费者线程从队列中按顺序取出订单进行处理减少数据库同时处理的请求量。
2. 提升系统响应速度
秒杀请求在异步架构中 同步部分快速返回秒杀结果例如秒杀资格校验。异步部分订单的具体处理如扣减库存、保存订单放到后台处理。这种分离让用户能快速得到响应而系统后台有更多时间处理复杂的订单逻辑。
异步优化的核心逻辑是什么
问为什么需要异步优化秒杀订单 答在高并发场景中秒杀会同时产生大量订单请求。如果直接将请求交给数据库处理容易导致数据库压力过大从而系统崩溃。异步优化通过使用阻塞队列将订单请求排队避免直接对数据库产生瞬时高负载。
问如何实现异步处理 答将订单信息保存到阻塞队列中使用单线程线程池中的线程从队列中按顺序取出订单进行处理。这样可以削峰填谷减轻数据库压力。 阻塞队列的特点是什么
问阻塞队列的作用是什么 答阻塞队列是线程安全的队列支持生产者-消费者模型。在代码中生产者是seckillVoucher方法它将订单信息加入阻塞队列消费者是VoucherOrderHandler线程它从队列中取出订单进行处理。
问为什么使用阻塞队列 答阻塞队列的特点是如果队列为空消费者线程会阻塞等待如果队列满了生产者线程会阻塞等待。这样可以很好地协调生产者和消费者的速度避免资源浪费或超负荷。 Lua脚本在这里的作用是什么
问为什么使用Lua脚本操作Redis 答Lua脚本在Redis中是原子执行的。使用Lua脚本可以保证秒杀资格验证和库存扣减的原子性避免并发问题。
问Lua脚本验证了什么 答
用户是否重复下单通过Redis中存储的用户信息判断。秒杀库存是否充足通过Redis中存储的库存数量判断。
-- 参数
-- 优惠券id
local voucherId ARGV[1]
-- 用户id
local userId ARGV[2]-- 数据key
local stockKey seckill:stock:.. voucherId
local orderKey seckill:order:.. voucherId-- 检查库存是否足够
if (tonumber(redis.call(get, stockKey)) 0) thenreturn 1 -- 库存不足
end-- 检查用户是否重复下单
if (redis.call(sismember, orderKey, userId) 1) thenreturn 2 -- 重复下单
end-- 减少库存并记录订单
redis.call(incrby, stockKey, -1)
redis.call(sadd, orderKey, userId)
return 0将秒杀券的库存以String形式存入Redis OverrideTransactionalpublic 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());} 异步调用创建订单的具体逻辑是什么
Override
public Result seckillVoucher(Long voucherId) {Long userId UserHolder.getUser().getId();// 1. 校验秒杀资格Long res stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());if (res ! 0) {// 秒杀资格校验失败return Result.fail(res 1 ? 库存不足 : 重复下单);}// 2. 生成订单信息VoucherOrder voucherOrder new VoucherOrder();long orderID redisIdWorker.nextId(order);voucherOrder.setId(orderID);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);// 3. 将订单信息放入阻塞队列orderTasks.add(voucherOrder);// 获取代理对象proxy (IVoucherOrderService) AopContext.currentProxy();return Result.ok(orderID);
}问seckillVoucher方法中发生了什么 答这是异步调用的入口逻辑分为以下几个步骤
验证秒杀资格 使用Lua脚本操作Redis确保原子性。判断用户是否重复下单或者库存是否不足。如果秒杀资格验证失败则直接返回错误信息。生成订单信息 使用RedisIdWorker生成订单ID。将订单信息用户ID、代金券ID等封装成VoucherOrder对象。将订单信息保存到阻塞队列 调用orderTasks.add(voucherOrder)将订单加入阻塞队列中。返回订单ID 在返回给用户订单ID时并没有真正完成订单而是进入队列等待处理。 为什么要用代理对象proxy调用createVoucherOrder方法
问为什么不直接调用createVoucherOrder
答因为 createVoucherOrder 方法是事务方法需要通过代理对象调用才能生效。 Spring 的事务机制基于 AOP面向切面编程实现 Spring 使用代理对象动态代理或 CGLIB 代理来拦截对事务方法的调用并在方法执行前后添加事务管理逻辑如开启事务、提交事务或回滚事务。如果直接调用类内部的事务方法调用不会经过代理对象而是直接执行原始方法Spring 的事务管理器无法介入导致事务逻辑失效。 内部调用的问题 在类的内部直接调用另一个事务方法时调用不会经过代理对象而是通过 this 调用因此事务拦截器不会生效事务注解Transactional失效。
问代理对象是如何获取的
将代理对象声明为一个成员变量通过 AopContext.currentProxy() 获取当前类的代理对象。原因AopContext.currentProxy() 返回的是 Spring AOP 生成的当前类的代理对象它能够拦截方法调用从而触发事务管理逻辑。在异步线程中直接调用当前类的方法时事务不会生效因为直接调用是通过 this 引用而不是代理对象调用。通过成员变量保存的代理对象即使在异步线程中调用方法也可以确保事务逻辑有效。最终通过代理对象调用 createVoucherOrder 方法可以正常触发 Spring 的事务管理器确保事务功能生效。 对于代码的详细解释
private static final ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor();private final BlockingQueueVoucherOrder orderTasks new LinkedBlockingQueue();
PostConstruct
private void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}private class VoucherOrderHandler implements Runnable {Overridepublic void run() {while (true) {try {// 获取队列当中的订单VoucherOrder voucherOrder orderTasks.take();handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error(Error processing order, e);}}}}
SECKILL_ORDER_EXECUTOR 是什么
SECKILL_ORDER_EXECUTOR 是一个 单线程线程池用来处理秒杀订单的异步任务。
private static final ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor();单线程线程池的特点是线程池中始终只有一个线程任务会按顺序执行适合需要顺序处理的场景。
它的主要作用是 管理和调度线程的生命周期。具体来说
启动和管理消费者线程
VoucherOrderHandler 需要一个线程不断运行用来从阻塞队列中取订单并处理。线程池 SECKILL_ORDER_EXECUTOR 的作用是启动这个线程并保证这个线程的生命周期由线程池管理。
线程复用
如果你手动创建线程new Thread()可能会导致系统频繁创建和销毁线程浪费系统资源。使用线程池可以复用线程减少线程的创建和销毁开销提高性能。
稳定性
如果 VoucherOrderHandler 线程在执行中意外退出例如抛出未捕获异常线程池会自动接管并重新启动线程保证任务不会中断。
在这里SECKILL_ORDER_EXECUTOR 通过单线程的方式从阻塞队列中取出订单按顺序处理确保秒杀订单的处理逻辑是线程安全的。 PostConstruct 是什么
PostConstruct 是 Java 的一个注解作用是在 Spring 容器将 Bean 初始化完成后立即执行标注的方法。换句话说当 Spring 加载并创建了 VoucherOrderServiceImpl 实例后会自动调用 init() 方法。
这是一个生命周期回调方法常用于初始化逻辑比如启动线程、加载配置等。 init() 方法的作用是什么
这个方法的主要作用是 启动一个专用线程由单线程线程池管理用于从阻塞队列中取出订单并进行异步处理。通过 SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());将 VoucherOrderHandler 提交到线程池中线程池会启动一个线程持续运行 VoucherOrderHandler 中的逻辑。 VoucherOrderHandler 是什么
VoucherOrderHandler 是一个内部类它实现了 Runnable 接口代表一个任务。
任务的核心逻辑是从阻塞队列中取出订单并处理。它的 run() 方法包含一个 while(true) 循环这样线程会一直运行不断从队列中取出订单通过 orderTasks.take()直到程序终止。
VoucherOrderHandler 调用的handleVoucherOrder
防止同一用户多次下单重复下单。调用执行订单的具体业务逻辑的方法createVoucherOrder 如扣减库存、保存订单等。 Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {// 实现一人一单我们需要先判断该用户是否已经抢过了// 根据优惠券id和用户id查询订单Long userId UserHolder.getUser().getId();int count query().eq(user_id, userId).eq(voucher_id, voucherOrder).count();if (count 0) {log.error(已经购买过不可重复购买);}// 扣减库存boolean success seckillVoucherService.update().setSql(stock stock - 1).eq(voucher_id, voucherOrder).
// eq(stock,voucher.getStock()). // 加个乐观锁如果现在的库存和我之前查询的库存不相同说明在我之前就有线程修改了数据库gt(stock, 0).update();if (!success) {log.error(库存不足);}// 写入数据库 不需要再返回orderId了因为之前在seckillVoucher已经返回了save(voucherOrder);}
数据库操作的注意点有哪些
问如何实现一人一单的限制 答在createVoucherOrder方法中通过查询数据库判断用户是否已经购买过对应的代金券。
问如何扣减库存 答使用seckillVoucherService执行SQL语句更新库存并通过gt(\stock\, 0)确保库存大于0。 整体逻辑总结 触发时机 当 VoucherOrderServiceImpl 被 Spring 加载并实例化后PostConstruct 注解标注的 init() 方法会被调用。 作用 init() 方法向线程池提交了一个 VoucherOrderHandler 任务这个任务会启动一个线程不断从阻塞队列中取出订单并调用相关处理逻辑handleVoucherOrder。 seckillVoucher 方法 单线程线程池、阻塞队列、seckillVoucher 和 VoucherOrderHandler 的协作过程总结
seckillVoucher 是厨师
它负责接收顾客的订单请求秒杀请求检查是否符合要求库存是否足够、是否重复下单然后生成订单菜品并放在桌子上阻塞队列。核心职责生产订单确保每个订单合法并生成完整订单信息。
BlockingQueue 是桌子
它负责临时存放厨师制作好的订单菜品保证每个订单都按顺序排列。如果桌子空了顾客消费者线程只能等如果桌子满了厨师生产者线程也需要暂停制作。核心职责缓冲区用于在生产和消费之间解耦。
VoucherOrderHandler 是顾客
它负责从桌子上取菜从队列中取订单并最终消费处理订单包括扣减库存、写入数据库等。如果桌子没有菜了它会耐心等待一旦有菜它会立刻取走并处理。核心职责消费订单执行订单处理逻辑。
SECKILL_ORDER_EXECUTOR 是服务员
它负责启动和管理顾客消费者线程确保顾客始终在桌子旁边等待取菜。如果顾客突然有事不要菜品了比如异常退出服务员会招待一个新的顾客来接替。核心职责管理消费者线程的生命周期确保订单处理不断运行。 方法调用流程总览
用户发起秒杀请求触发 seckillVoucher 方法。seckillVoucher 验证秒杀资格并将订单放入阻塞队列。VoucherOrderHandler由线程池管理的消费者线程从队列中取出订单调用 handleVoucherOrder 进行处理。handleVoucherOrder 利用分布式锁防止重复下单并调用 createVoucherOrder 完成订单的核心逻辑。createVoucherOrder 执行订单的最终处理包括扣减库存、写入数据库等。
方法逻辑一览表
方法作用关键逻辑seckillVoucher秒杀请求入口生成订单并加入阻塞队列验证秒杀资格生成订单信息加入阻塞队列。阻塞队列 (BlockingQueue)存储订单信息实现生产者与消费者的解耦线程安全存储缓冲生产者和消费者速度差异。VoucherOrderHandler消费者线程从队列中取订单并调用处理方法从队列取订单调用 handleVoucherOrder。handleVoucherOrder防止重复下单调用核心业务逻辑创建分布式锁防止重复下单调用 createVoucherOrder。createVoucherOrder执行订单的核心逻辑校验订单、扣减库存、保存订单到数据库。 完整代码
Service
RequiredArgsConstructor
public class VoucherOrderServiceImpl extends ServiceImplVoucherOrderMapper, VoucherOrder implements IVoucherOrderService {Resourceprivate ISeckillVoucherService seckillVoucherService;final RedisIdWorker redisIdWorker;final StringRedisTemplate stringRedisTemplate;final RedissonClient redissonClient;private static final DefaultRedisScriptLong SECKILL_SCRIPT;static {SECKILL_SCRIPT new DefaultRedisScript();SECKILL_SCRIPT.setLocation(new ClassPathResource(seckill.lua));SECKILL_SCRIPT.setResultType(Long.class);}private static final ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor();private IVoucherOrderService proxy;private final BlockingQueueVoucherOrder orderTasks new LinkedBlockingQueue();PostConstructprivate void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable {Overridepublic void run() {while (true) {try {// 获取队列当中的订单VoucherOrder voucherOrder orderTasks.take();handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error(Error processing order, e);}}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {Long userId voucherOrder.getUserId();// 1. Create lockRLock lock redissonClient.getLock(lock:order: userId);// 2. Try to acquire lockboolean isLock lock.tryLock();if (!isLock) {log.error(Duplicate order not allowed);return;}try {// 3. Create order via proxyproxy.createVoucherOrder(voucherOrder);} finally {// 4. Release locklock.unlock();}}/*** 基于异步Lua脚本保证原子性** param voucherId* return*/Overridepublic Result seckillVoucher(Long voucherId) {Long userId UserHolder.getUser().getId();// 执行Lua脚本Long res stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());// 判断返回值是否为0if (res ! 0) {// 非0 则没有秒杀资格return Result.fail(res 1 ? 库存不足 : 重复下单);}// 从Redis当中获取下单信息long orderId redisIdWorker.nextId(order);// TODO 为0 表示有秒杀资格 需要将下单信息保存在阻塞队列当中// 创建订单VoucherOrder voucherOrder new VoucherOrder();// 订单idlong orderID redisIdWorker.nextId(order);voucherOrder.setId(orderID);// 用户idvoucherOrder.setUserId(UserHolder.getUser().getId());// 代金券idvoucherOrder.setVoucherId(voucherId);// 保存到阻塞队列当中orderTasks.add(voucherOrder);// 获取代理对象proxy (IVoucherOrderService) AopContext.currentProxy();return Result.ok(orderId);}Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {// 实现一人一单我们需要先判断该用户是否已经抢过了// 根据优惠券id和用户id查询订单Long userId UserHolder.getUser().getId();int count query().eq(user_id, userId).eq(voucher_id, voucherOrder).count();if (count 0) {log.error(已经购买过不可重复购买);}// 扣减库存boolean success seckillVoucherService.update().setSql(stock stock - 1).eq(voucher_id, voucherOrder).
// eq(stock,voucher.getStock()). // 加个乐观锁如果现在的库存和我之前查询的库存不相同说明在我之前就有线程修改了数据库gt(stock, 0).update();if (!success) {log.error(库存不足);}// 写入数据库save(voucherOrder);}
}