《黑马点评》异步秒杀优化|消息队列
异步秒杀优化
异步:
比如你早上起床后边吃早饭边看电视(异步),而不是吃完饭再看电视(同步)。很好理解吧,反着来就行。
参考链接
Web server failed to start. Port 8082 was already in use.解决办法,今早上没搞定现在看这篇文章搞定了。
写完了,给我头都绕晕了,在这做一个总结:
秒杀业务的优化思路是什么?
先利用Redis完成库存余量、一人一单判断,完成抢单业务 再将下单业务放入阻塞队列,利用独立线程异步下单
基于阻塞队列的异步秒杀存在哪些问题?
内存限制问题 数据安全问题
任何人来调用创建订单逻辑,进入到seckillVoucher
先去调用lua脚本,尝试判断用户有没有购买资格,然后进行一系列操作
@Override
public Result seckillVoucher(Long voucherId) {
//获取用户
Long userId = UserHolder.getUser().getId();
// long orderId = redisIdWorker.nextId("order");
//1.执行lua脚本
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),//空集合
voucherId.toString(), userId.toString()//传入两个参数
);
int r = result.intValue();//转型
//2.判断结果是否为0
if (r != 0) {
// 2.1.不为0 ,代表没有购买资格
return Result.fail(r == 1 ? "库存不足" : "不能重复下单");
}
//这里代表有购买资格 把下单信息保存导阻塞队列
//todo:保存到阻塞队列
VoucherOrder voucherOrder = new VoucherOrder();
// 2.3.订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
// 2.4.用户id
voucherOrder.setUserId(userId);
// 2.5.代金券id
voucherOrder.setVoucherId(voucherId);
// 2.6.放入阻塞队列
orderTasks.add(voucherOrder);
//3.获取代理对象
//获取代理对象 事务
proxy = (IVoucherOrderService) AopContext.currentProxy();
//3.返回订单id
return Result.ok(orderId);
}
放入阻塞队列之后要去执行义务下单,这个任务认为百分百成功,因为库存是足的,怎么操作?
1.初始化时执行线程池,线程池业务就是从这个组合队列不断的取出这个订单,去创建。
// 用于线程池处理的任务
// 当初始化完毕后,就会去从对列中去拿信息
private class VoucherOrderHandler implements Runnable {
@Override
public void run() {
while (true) {
try {
//1. 获取队列中的订单信息
VoucherOrder voucherOrder = orderTasks.take();
//2. 创建订单
handleVoucherOrder(voucherOrder);//定义函数
} catch (InterruptedException e) {
log.error("处理订单异常", e);//这里可能有异常 上边加上日志
}
}
}
}
2.创建的流程:获取锁什么的,锁拿到去创建订单
/*
方法前面void的不再返回给前端仍任何东西了 因为已经做异步处理了
*/
private void handleVoucherOrder(VoucherOrder voucherOrder) {
//1.获取用户id,不能从ThreadLocal中取,因为是从线程池获取新线程
Long userId = voucherOrder.getUserId();
//2.创建锁对象
RLock lock = redissonClient.getLock("lock:order:" + userId);
//获取锁
boolean isLock = lock.tryLock();
if (!isLock) {
//获取锁失败 返回错误或重试
log.error("不允许重复下单");
return;
}
try {
//获取代理对象 (事务)
//作为一个子线程没办法从ThreadLocal中取出
proxy.createVoucherOrder(voucherOrder);
} finally {
//释放锁
lock.unlock();
}
}
3.创建订单:一人一单进来,做一个判断是否存在,做一个库存判断 ,创建订单
@Transactional
public void createVoucherOrder(VoucherOrder voucherOrder) {
// 5.一人一单逻辑
// Long userId = UserHolder.getUser().getId();
Long userId = voucherOrder.getUserId();
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
// 5.2判断是否存在
if (count > 0) {
//用户已经购买过了
log.error("用户已经购买过一次了");//和前面一样不太可能返回了直接写个日志就行
return;
}
// 6.扣减库存
// boolean success = seckillVoucherService.update()
// .setSql("stock= stock -1") //set stock = stock -1
// .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); //where id = ? and stock = ?
boolean success = seckillVoucherService.update()
.setSql("stock= stock -1")
.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0).update(); //where id = ? and stock > 0
if (!success) {
//扣减库存
log.error("库存不足!");
return;
}
// 8.保存订单
save(voucherOrder);
// return Result.ok(orderId);
}
消息队列
消息队列:
就是我们常说的MQ,英文叫Message Queue,是作为一个单独的中间件产品存在的,独立部署。
参考链接
zhuanlan.zhihu.com/p/431149128
基于Stream的消息队列
Redis消息队列 基于Stream消息队列实现异步秒杀
下面的我听不懂了,就自己跟着写了一下脚本改了一下代码太绕了,等我有时间又回来重新学吧。
转载自:https://juejin.cn/post/7370841518791475235