date: 2024-10-06 20:53:47 title: 黑马点评笔记 author: zaqai tags:
- 分布式
- 数据库
- redis
- springboot
优惠券秒杀
分布式下生成全局唯一 ID 见[[分布式系统生成全局唯一ID]]
优惠券下单时, 先判断秒杀是否开始, 再判断库存是否不为 0, 再库存减一, 创建订单表
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否开始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
// 尚未开始
return Result.fail("秒杀已经结束!");
}
// 4.判断库存是否充足
if (voucher.getStock() < 1) {
// 库存不足
return Result.fail("库存不足!");
}
//5,扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock= stock -1")
.eq("voucher_id", voucherId).update();
if (!success) {
//扣减库存
return Result.fail("库存不足!");
}
//6.创建订单
VoucherOrder voucherOrder = new VoucherOrder();
// 6.1.订单id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
// 6.2.用户id
Long userId = UserHolder.getUser().getId();
voucherOrder.setUserId(userId);
// 6.3.代金券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
return Result.ok(orderId);
}
超卖问题 (多线程安全)
使用 apifox 启动 200 个线程向秒杀接口发请求(库存为 100), 会出现超卖, 也就是库存出现负数 多线程安全问题
-> 加锁
- 悲观锁: 每次操作都认为会出现不安全问题, 所以先加锁 重
- 乐观锁: 每次操作都先操作, 再判断线程安全问题
版本号法, 每次对数据的操作, 都在该行数据的 version 字段加一, 保证之前查询的数据没有被更改过
乐观锁的两种方案
- 先查库存, 再库存减一时判断, 只有此时库存和之前查的库存一致时才减一 同样 200 个线程同时抢 100 个优惠券, 会出现大量失败 (超过 50%), 库存最后也不为 0 因为判断条件有点严苛
- 库存减一时判断, 只要此时库存大于 0 就减一
200 个线程, 成功率 50%
一人一单
可以在库存减一之前, 先判断优惠券订单表里有没有这个 id 的用户购买这个优惠券的记录, 有的话就 return fail 同样会有线程安全问题, 多个线程同时查询, 都没有查到记录, 之后多个线程都能秒杀成功
不能使用乐观锁了, 因为数据本来都不存在, 没法根据版本号判断, 要用悲观锁
乐观锁比较适合更新数据
在使用锁过程中,控制锁粒度 是一个非常重要的事情
Long userId = UserHolder.getUser().getId();
synchronized(userId.toString().intern()){
}
相同 userId
进来就会因为获取不到锁无法同时执行, userId.toString().intern()
参考 [[Java字符串的几种创建方式及原理]]
redis 分布式锁
集群环境下, 每一份后端服务都有自己的运行空间, 通过 nginx 反向代理, 同一个用户的多个相同请求可能会分配到不同节点上 使用 JVM 提供的锁也只能锁住自己的资源,这时, 还是有可能一人多单
分布式锁需要满足在分布式环境下, 多个线程都可见而且互斥
回复