【高并发】超卖一人一单问题
创始人
2024-03-25 12:58:18
0

一、超卖问题

1. 超卖场景

高并发场景下用户下单,存在如下所示的超卖问题,其产生的主要原因是一个线程刚读出库存值,还没进行修改时,另一个线程也读出来该库存值,从而导致这两个线程在进行下单时,对同一个值减了1。

在这里插入图片描述

2. 解决方案

1. 悲观锁

认为线程安全问题一定会发生,线程串行执行

  • 优点:简单粗暴
  • 缺点:性能一般

2. 乐观锁

认为线程安全问题不一定会发生,在更新数据时判断有没有线程对数据做了修改

  • 优点:性能好
  • 缺点:存在成功率低的问题,多个线程同时访问,查询到同一个值,只要有一个线程进行了修改,其他的线程就都失败了

在这里插入图片描述

3. 乐观锁的实现

难点在于判断数据是否被修改过,判断方式有:

1. 版本号法

给数据加一个版本号
在这里插入图片描述

在这里插入图片描述

先查询到版本号和库存,更新的时候判断版本号和查询时的版本号是否相同,不相同说明这段时间库存已经被更新过了,此时更新库存失败。

2. CAS 法

库存和版本号执行相同操作,用数据本身是否有变化进行判断。先查询库存数据的值,更新时再查一遍看看库存数据有没有变化,有变化就不更新了。

在这里插入图片描述

使用 CAS 法 解决超卖问题
        // 5. 扣除库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock", voucher.getStock()) // 乐观锁,保证当前线程执行的时候没有其他线程修改过库存.update();

解决失败率高的问题: 只要库存大于 0 就卖

        // 5. 扣除库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0) // 对乐观锁进行改进,只要防止出现负数就行.update();

二、一人一单

1. 场景

只容许一人下一单,要对单个用户访问的高并发情况加锁

2. 基于悲观锁的实现

    @Overridepublic 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("库存不足");}// 线程安全 —— 先获取锁,完成事务,释放锁Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) { // 对用户 id 加锁,保证字符串值相同就会被锁定IVoucherOrderService proxy = (IVoucherOrderService)AopContext.currentProxy(); // 拿到当前对象的代理对象return proxy.createVoucherOrder(voucherId); // 当前没有事务功能,代理对象才有事务功能}}@Transactional// 实现一人一单 —— 悲观锁 —— 以用户 id 加锁 —— 处理同一个用户的并发安全问题,防止一个用户并发买很多单public Result createVoucherOrder(Long voucherId){Long userId = UserHolder.getUser().getId();// 查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if (count > 0) {return Result.fail("您已经买过一次了,不能再买了");}// 5. 扣除库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId)//.eq("stock", voucher.getStock()) // 乐观锁,保证当前线程执行的时候没有其他线程修改过库存.gt("stock", 0) // 对乐观锁进行改进,只要防止出现负数就行.update();if (!success) {return Result.fail("库存不足");}// 6. 创建订单VoucherOrder voucherOrder = new VoucherOrder();// 订单的参数 —— 订单 id \ 代金券 id \ 用户 id// 生成唯一 idlong id = redisIdWorker.nextId("order");voucherOrder.setId(id);voucherOrder.setVoucherId(voucherId);voucherOrder.setUserId(1L);save(voucherOrder);return Result.ok();}

导入依赖:

        org.aspectjaspectjweaver

加入允许暴露代理对象的依赖:

@EnableAspectJAutoProxy(exposeProxy = true) // 表示开启暴露代理对象
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {public static void main(String[] args) {SpringApplication.run(HmDianPingApplication.class, args);}}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...