【高并发】超卖一人一单问题

news2024/11/17 13:40:42

一、超卖问题

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. 基于悲观锁的实现

    @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("库存不足");
        }

        // 线程安全 —— 先获取锁,完成事务,释放锁
        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
            // 生成唯一 id
            long id = redisIdWorker.nextId("order");
            voucherOrder.setId(id);
            voucherOrder.setVoucherId(voucherId);
            voucherOrder.setUserId(1L);
            save(voucherOrder);
            return Result.ok();
    }

导入依赖:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

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

@EnableAspectJAutoProxy(exposeProxy = true) // 表示开启暴露代理对象
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {

    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/70597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【无人机】基于Matlab实现四旋翼无人机几何跟踪控制

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

红星美凯龙寻找「反弹线」

文|螳螂观察 作者| 青月 疫情三年&#xff0c;绝大多数企业都处在「水深火热」之中&#xff0c;到今天&#xff0c;这个情况仍未得到改善。 作为自媒体&#xff0c;「螳螂观察」的感触颇深&#xff0c;疫情之前对一家企业表示肯定的词一般是「暴涨」、「同比大增」&#xff…

C语言竞赛

目录 1059:C语言竞赛 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 思路: 1.结构: 2.判断素数 3.输出宽度调整 代码: 时间复杂度: 总结: 题目链接: 1059:C语言竞赛 C 语言竞赛是浙江大学计算机学院主持的一个欢乐的竞赛。…

vue3+vite使用element-plus

前言&#xff1a; vue3vite的项目中使用 element-plus的教程。 官方地址&#xff1a; 安装 | Element Plusa Vue 3 based component library for designers and developershttps://element-plus.gitee.io/zh-CN/guide/installation.html#%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9B%B…

考研408数据结构 · 开端

引言数据结构在学什么对C语言掌握要求数据结构三要素逻辑结构数据运算存储结构数据结构在学什么 如何把问题信息化 场景&#xff1a;现在需要将1000本不同类型的书摆放到图书馆的书架上。研究问题&#xff1a;采取什么办法摆放&#xff1f;方法有&#xff1a; ① 不管不顾从头…

进程优先级与环境变量

目录 一、进程优先级 1.优先级与权限 2.查看进程优先级 3.PRI与NI 4.修改进程的优先级 5.进程优先级的注意事项 二、进程的其他概念 1.竞争性 2.独立性 3.并行和并发 三、环境变量 1.什么是环境变量 2.环境变量的分类 3.查看环境变量内容 &#xff08;1&#xf…

uniapp easycom教程

easycom 是 uniapp 的一种组件自动引入的规则&#xff0c;使用这种规则可以使满足规则的组件无需注册直接使用。 接下来我们来看一眼效果 这里可以看到我并没有进行组件注册而是直接使用了组件&#xff0c;这样的效果就是通过 easycom 的自定义规则来实现的。 来看一眼我的自…

pandas模块使用介绍

pandas模块使用介绍1.pandas简介 ​ pandas 是基于NumPy 的一种工具&#xff0c;该工具是为解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型&#xff0c;提供了高效地操作大型数据集所需的工具。pandas提供了大量能使我们快速便捷地处理数据的函数和方法。…

专访 | 徐鹏程:开源,就是酷

OpenMLDB&#xff1a; 请先来一段自我介绍吧。 徐鹏程&#xff1a; 我本科就读于上海交通大学&#xff0c;硕士在伊利诺伊大学香槟分校&#xff0c;专业都是电子与计算机工程&#xff0c;感兴趣的方向有机器学习在生物信息等领域的应用、计算机系统与架构、分布式系统等。平时…

Java数据结构与Java算法学习Day08---关于树的深度学习(简略笔记记录)

目录 一、平衡树 119 1.1 2-3查找树 119 1.1.1 2-结点和3-结点的含义 119 1.1.2查找 120 1.2 2-3查找树的插入 121 1.2.1 向2-结点中插入新建 121 1.2.2向一棵树只含有一个3-结点的树中插入新建 121 1.2.3向一个父结点为2-结点的3-结点中插入新建 121 1.2.4向一个父…

数据库建表设计技巧

1.名字 建表的时候&#xff0c;给表、字段和索引起个好名字&#xff0c;真的太重要了。 1.1 见名知意 名字就像表、字段和索引的一张脸&#xff0c;可以给人留下第一印象。 好的名字&#xff0c;言简意赅&#xff0c;见名知意&#xff0c;让人心情愉悦&#xff0c;能够提高…

基于go-micro微服务的实战-zipkin实现全链路追踪(九)

基于go-micro微服务的实战-zipkin实现全链路追踪(九) 文章最后附带完整代码 Zipkin是 Twitter 的一个开源项目&#xff0c;基于 Google Dapper实现。可以使用它来收集各个服务器上请求链路的跟踪数据。除了面向开发的API接口之外&#xff0c;它也提供了方便的 UI 组件帮助我们…

代码随想录算法训练营第五十六天| LeetCode583. 两个字符串的删除操作、LeetCode72. 编辑距离

一、LeetCode583. 两个字符串的删除操作 1&#xff1a;题目描述&#xff08;583. 两个字符串的删除操作&#xff09; 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 2&#xff1a;解题思…

细数一下Java中反射机制及反射的优缺点

1. 反射的概念 反射 机制指的是&#xff0c;程序在运行时能够获取自身的信息。在 java 中只要给定类的名字&#xff0c;就能够获取类的所有属性和方法。 反射是 Java 中很多高级特性的基础&#xff0c;比如 注解、动态代理 以及 Spring Ioc、AOP 等技术都需要借助反射来实现。…

再见 if…elif…!使用 Python 装饰器轻松搞定!

大家好&#xff0c;今天在 Github 阅读 EdgeDB[1] 的代码&#xff0c;发现它在处理大量if…elif…else的时候&#xff0c;巧妙地使用了装饰器&#xff0c;方法设计精巧&#xff0c;分享给大家一下&#xff0c;欢迎收藏学习&#xff0c;喜欢点赞支持&#xff0c;技术交流见文末。…

DJ13-1 汇编语言程序设计-4

目录 1. 带显示的键盘输入&#xff08;1 号功能&#xff09; 2. 不带显示的键盘输入&#xff08;8 号功能&#xff09; 3. 字符串输入&#xff08;0AH 号功能&#xff09; 4. 字符显示&#xff08;2 号功能&#xff09; 5. 字符串显示&#xff08;9 号功能&#xff09; 6…

【C++哈希表的基础使用记录】

前言 今天重新打开力扣&#xff0c;看到以前的签到题两数之和&#xff0c;以前的方法是双指针暴力解法&#xff0c;偶然看到了哈希表的方法&#xff0c;让我想起了iOS的字典&#xff0c;也顺带学习了哈希表的使用&#xff0c;我这里仅仅限于自己用来写算法题&#xff0c;作以记…

从源码角度看React-Hydrate原理

React 渲染过程&#xff0c;即ReactDOM.render执行过程分为两个大的阶段&#xff1a;render 阶段以及 commit 阶段。React.hydrate渲染过程和ReactDOM.render差不多&#xff0c;两者之间最大的区别就是&#xff0c;ReactDOM.hydrate 在 render 阶段&#xff0c;会尝试复用(hydr…

西门子1200PLC中OB,FC,FB,DB

1.基础知识 临时变量&#xff1a;存储在L堆栈中&#xff0c;块执行结束后&#xff0c;变量消失&#xff1b; 静态变量&#xff1a;存储在背景数据块中&#xff0c;块调用结束后&#xff0c;变量被保留&#xff1b; &#xff08;1&#xff09;OB组织块 OB1&#xff08;MAIN&a…