解决超卖问题

【项目实战】如何优美的处理电子商务领域的“超卖”(Overselling)问题 在电子商务领域,“超卖”(Overselling)通常指的是库存管理中的一种错误情况。在电子商务领域,“超卖”(Overselling)即电商平台或在线商店销售的商品数量超过了实际库存的数量。在电子商务领域,“超卖”(Overselling)是电商运营中需要尽量避免的问题,有效的库存管理和系统优化是防止超卖的关键。通过建立稳健的库存控制系统和应急计划,商家可以减少超卖的发生,维护良好的客户关系和品牌形象。 阅读详情
AI摘要

在电商、秒杀等高并发场景中,超卖问题是经典挑战。本文基于Spring Boot项目,分析超卖问题的根本和技术原因,介绍数据库乐观锁、Redis原子操作、Redisson分布式锁、消息队列等解决方案,给出综合方案建议,并提及幂等性、库存回滚等注意事项。

在电商、秒杀等高并发场景中,超卖问题(库存扣减时出现负数) 是一个经典的技术挑战。以下是基于 Spring Boot 项目的 超卖问题解决方案,结合多种技术手段实现高并发下的库存安全。


1. 超卖问题原因分析

  • 根本原因:多个并发请求同时读取库存(如剩余 1 件),都执行扣减操作,导致实际库存扣减多次(如变为 -1)。
  • 技术原因:数据库或缓存的 非原子操作 和 并发控制缺失。

2. 解决方案总览

方案原理说明适用场景优缺点
数据库乐观锁通过版本号或时间戳控制并发更新低并发,数据一致性要求高简单,但重试率高
数据库悲观锁使用 SELECT ... FOR UPDATE 锁定记录极低并发,强一致性性能差,易死锁
Redis 原子操作利用 Redis 的 DECR/INCR 原子性扣减库存高并发,允许短暂不一致高性能,需处理数据同步
分布式锁使用 Redisson 等分布式锁控制同一商品的库存操作分布式系统,高并发性能中等,实现复杂
消息队列将请求放入队列串行处理,异步扣减库存超高并发,最终一致性高吞吐,延迟较高

3. 具体实现方案

方案一:数据库乐观锁(版本号法)

原理:通过版本号或时间戳校验,确保库存扣减的原子性。需要有一个数据可以标识在查询到的数据和在进行数据库更新的时候的数据是相同的。(改进可以用库存来标识)

步骤:
  1. 表结构设计:

    CREATE TABLE product (
      id BIGINT PRIMARY KEY,
      stock INT NOT NULL,
      version INT DEFAULT 0  -- 乐观锁版本号
    );
    
  2. 更新逻辑:

    @Transactional
    public boolean deductStock(Long productId, int quantity) {
        // 1. 查询商品信息(带版本号  版本号就是一个标识)
        Product product = productDao.findById(productId);
        // 2.如果查询到的库存数量小于用户购买的数量直接返回 库存不足
        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }
    
        // 3. 执行更新(版本号校验)在更新数据库的时候通过查看标识是否发生变化来确保是否可以去修改数据库
        int rows = productDao.updateStock(
            productId, 
            quantity, 
            product.getVersion()
        );
    
        // 3. 更新成功判定
        return rows > 0;
    }
    
  3. SQL 语句:

    UPDATE product 
    SET stock = stock - #{quantity}, 
        version = version + 1 
    WHERE id = #{productId} 
      AND version = #{oldVersion}
      AND stock >= #{quantity};
    

优点:简单易实现,无需额外组件。
缺点:高并发下失败率高,需前端配合重试。

如果是通过库存来去标识的那么在数据库更新的时候不一定是让这个库存和前面查到的完全一样,可以让他大于0都可以进行更新。


方案二:Redis 原子操作 + 数据库最终一致(推荐高性能方案)

原理:使用 Redis 预扣库存(原子操作),异步同步到数据库。

步骤:
  1. 初始化库存到 Redis:

    // 商品ID:1001 初始库存100
    redisTemplate.opsForValue().set("stock:1001", 100);
    
  2. 扣减库存(原子操作):

    public boolean deductStock(Long productId, int quantity) {
        String key = "stock:" + productId;
        // 使用 Lua 脚本保证原子性
        String script = "if (redis.call('exists', KEYS[1]) == 1) then " +
                       "    local stock = tonumber(redis.call('get', KEYS[1])); " +
                       "    if (stock >= tonumber(ARGV[1])) then " +
                       "        return redis.call('decrby', KEYS[1], ARGV[1]); " +
                       "    end; " +
                       "    return -1; " +
                       "end; " +
                       "return -2;";
        
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(key),
            String.valueOf(quantity)
        );
    
        if (result == null) return false;
        return result >= 0;
    }
    
  3. 异步同步到数据库:

    @Async
    public void asyncUpdateDBStock(Long productId, int quantity) {
        productDao.deductStock(productId, quantity); // 数据库扣减(需处理幂等)
    }
    
  4. 补偿机制(防止 Redis 扣减成功但数据库失败):

    • 定期对比 Redis 和数据库库存差异,修复数据。
    • 订单超时未支付时回滚库存。

优点:高性能,适合秒杀场景。
缺点:需维护 Redis 和数据库的数据一致性。


方案三:Redisson 分布式锁(推荐分布式系统)

原理:通过分布式锁确保同一商品的库存扣减串行化。

步骤:
  1. 添加 Redisson 依赖:

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.23.2</version>
    </dependency>
    
  2. 配置 Redisson:

    spring:
      redis:
        host: localhost
        port: 6379
    
  3. 扣减库存逻辑:

    @Autowired
    private RedissonClient redissonClient;
    
    public boolean deductStock(Long productId, int quantity) {
        String lockKey = "product_lock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试加锁(等待10秒,锁自动释放时间30秒)
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                Product product = productDao.findById(productId);
                if (product.getStock() >= quantity) {
                    productDao.deductStock(productId, quantity);
                    return true;
                }
                return false;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();
        }
        return false;
    }
    

优点:强一致性,适合分布式环境。
缺点:性能受锁竞争影响,需合理设计锁粒度。


方案四:消息队列削峰填谷(推荐超高并发)

原理:通过消息队列缓冲请求,异步处理订单。

步骤:
  1. 发送扣减请求到队列:

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendDeductMessage(Long productId, int quantity) {
        Map<String, Object> message = new HashMap<>();
        message.put("productId", productId);
        message.put("quantity", quantity);
        rabbitTemplate.convertAndSend("stock.deduct.queue", message);
    }
    
  2. 消费者处理库存扣减:

    @RabbitListener(queues = "stock.deduct.queue")
    public void handleDeductMessage(Map<String, Object> message) {
        Long productId = (Long) message.get("productId");
        int quantity = (int) message.get("quantity");
        productDao.deductStock(productId, quantity); // 需保证幂等性
    }
    

优点:吞吐量极高,系统解耦。
缺点:数据最终一致,不适合实时响应。


4. 综合方案建议

  • 秒杀场景:
    Redis 预扣库存 + 消息队列异步下单 + 数据库最终扣减

    1. 用户请求扣减 Redis 库存(原子操作)。
    2. 库存足够时,发送消息到队列异步创建订单。
    3. 消费者从队列读取消息,扣减数据库库存。
  • 普通高并发:
    Redisson 分布式锁 + 数据库乐观锁

    1. 使用分布式锁控制商品粒度的并发。
    2. 数据库乐观锁保证最终一致性。

5. 注意事项

  • 幂等性:所有操作需保证幂等(如订单ID去重),防止重复扣减。
  • 库存回滚:订单取消或支付超时后,需回滚 Redis 和数据库库存。
  • 监控告警:监控库存异常、Redis 与数据库一致性差异。
  • 压测验证:使用 JMeter 模拟高并发,验证库存准确性。

通过合理选择方案,结合 Spring Boot 的事务管理、Redis 原子操作和分布式锁,可有效解决超卖问题。

Redis解决秒杀中的超卖问题 前言 在上一篇,我们通过一个简单的案例,分享了怎么利用redis设计并实现一个秒杀抢购的功能,关于秒杀功能中,需要注意的比较关键的有两个问题 高并发场景下,怎么确保不会超卖 高并发场景下,如何确保一人一单 具体在设计的时候,需要结合实际的项目和业务场景,比如1000个人抢购50件商品的时候,抢购是一方面,抢购完毕之后还要下单,这个下单的业务是和抢购放在一起呢还是单独处理,需结合具体场景以及系统的架构进行分析 下面用一个更通俗的业务场景来比较全面的 业务场景描述 某些商家为了吸引客户进店消费,推出线上发放 阅读详情

相关推荐

单细胞转录组数据质控实战:如何用Seurat精准过滤低质量细胞(附完整代码)

本文详细介绍了使用Seurat进行单细胞转录组数据质控的实战方法,包括基因检出数、分子计数和线粒体基因比例等关键指标的解读与可视化技巧。通过动态阈值策略和高级质控技巧(如双细胞检测和红细胞过滤),帮助研究者精准过滤低质量细胞,提升数据分析可靠性。附完整代码示例,适合生物信息学研究人员参考实践。

ii567的博客 825

商城超卖问题的几种解决方案

一、描述:        商城设计的过程中,必然会考虑到一个库存扣除的问题,超卖将会带来一定的损失和麻烦,在某一个时间段的瞬间的流量会造成库存的并发性操作带来库存超额扣除。针对这类的问题,我先来分析原因吧。 二、为什么会被超卖 只有弄清楚为什么会被超卖才能解决超卖问题,这里我们要弄清楚mysql的默认的扣除方式,update 表 store=store-1 where id=指定的id 首先我们看这样的语句有哪些问题呢? 库存有可能扣到

yongde123的专栏 1万+

量化爬虫实战:手把手教你搞定同花顺问财的hexin-v参数(附JS逆向思路与Cookie Hook技巧)

本文详细解析了如何通过JS逆向技术获取同花顺问财平台的`hexin-v`参数,包括Cookie Hook技巧和完整解决方案架构。从环境搭建到参数定位,再到生成逻辑逆向,手把手教你突破数据获取限制,为量化交易提供可靠数据支持。

weixin_42557803的博客 398

秒杀:超卖问题(图解+秒懂+史上最全)

文章很长,而且持续更新,建议收藏起来,慢慢读! Java 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 免费赠送 经典图书 : 极致经典 + 社群大片好评 《 Java 高并发 三部曲 》 面试必备 + 大厂必备 + 涨薪必备 免费赠送 经典图书 : 《Netty Zookeeper Redis 高并发实战》 面试必备 + 大厂必备 +涨薪必备 (加尼恩领取) 免费赠送 经典图书 : 《SpringCloud、Nginx高并发核心编程》 面试必备 + 大

架构师尼恩 2万+

stm32h743 FM25CL64铁电存储器踩坑记录

调试FRAM经验总结: 1、跑完FRAM读写代码之后,逻分仪发现总线上只有简单、短暂的电平变化,明显不是SPI的通信数据。 (经提醒发现连片选拉高拉低都没有执行到位) 后来发现是因为片选引脚没有初始化,所以直接拉高拉低是无效的。 2、引脚成功初始化后发现CLK、MISO、MOSI数据仍不正常。通过加打印的方式发现跑到HAL_SPI_Transmit中之后还没有执行到写SPI数据寄存器的那一步就return了。 后来经提醒发现: 上图中的指针使用方式错误 后来又发现: 打印出来发现

kuniqiw的博客 3844

移动端UI自动化测试新框架Maestro:声明式语法与实战指南

UI自动化测试是保障移动应用质量的关键环节,其核心原理是通过脚本模拟用户操作,对应用界面进行自动验证。传统命令式框架(如基于Selenium WebDriver的Appium)虽然控制粒度细,但脚本编写和维护成本较高。声明式测试框架则通过描述“做什么”而非“怎么做”,显著降低了技术门槛和维护复杂度,其技术价值在于提升测试脚本的可读性、编写效率与跨团队协作能力。在移动端测试领域,这尤其适用于需要快速验证核心业务流程、进行冒烟测试和回归测试的场景。本文聚焦的**Maestro框架**,正是这一理念的杰出实践。它

weixin_32921023的博客 713

超卖问题解决大全

文章目录什么是库存超卖??方式一:扣减库存时限制库存量大于0方式二:悲观锁方式三:乐观锁方式四:将请求放队列方式五:通过事务控制超卖方式六使用redis分布式来解决(悲观锁)基于redis实现分布式锁基于redisson开源框架实现redis分布式锁的缺点redis分布式锁优化redis分布式锁优化缺点方式七使用redis的原子操作解决超卖共享锁和排他锁共享锁和排他锁的加锁原则Spring事务-数...

fly_miqiqi的博客 1万+

EXCEL VBA从入门到精通 第一章:VBA入门

在VBA中,需要使用关键字Dim来定义一个变量,后面跟上变量名和变量的数据类型。例如,下面的代码定义了一个整型变量x和一个字符串变量name在VBA中,使用关键字Const来定义常量,后面跟上常量名和常量的值。例如,下面的代码定义了一个整型常量MAX_NUM和一个字符串常量MESSAGE需要注意的是,常量一旦被定义,其值就不能被改变。

1万+

【Redis】电商项目秒杀问题之超卖问题与一人一单问题

在如双11等购物需求剧增的背景下,一个物品库存里有100件但是由于并发等问题可能会导致该物品被卖出超过100件。这就是超卖问题,他是由于库存量被高并发请求而产生的线程安全问题。

qq_61903414的博客 4616

基于MATLAB的最大重叠离散小波变换(MODWT)算法详解

最大重叠离散小波变换(Maximal Overlap Discrete Wavelet Transform, MODWT)是DWT的重要改进形式,由Percival和Walden等人在20世纪90年代系统提出并完善。MODWT通过消除下采样操作,采用样本平移不变的分解方式,解决了DWT的多个固有缺陷。MODWT的核心创新在于取消下采样环节。与DWT每级分解后对滤波输出进行二分下采样不同,MODWT在每一级都使用相同的滤波器,但通过插入零值的方式调整滤波器系数,保持每级系数数量与原始信号相同。

qq_42212808的博客 115

最详细的 SAP ABAP Web Service 创建和消费步骤讲解

本文原版发表于 SAP 官方社区,当时用的英文版写作,标题为:Step by step to create, consume and trace web service in ABAP system https://blogs.sap.com/2014/05/20/step-by-step-to-create-consume-and-trace-web-service-in-abap-system/ 总共阅读量超过 18 万。 How to create web service provider in A

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 3150

超卖问题为什么会出现以及解决超卖问题的几种方案

解决超卖问题是高并发系统中的关键任务之一。数据库事务隔离级别:使用串行化隔离级别来确保数据一致性,但会牺牲性能。悲观锁:通过行级锁防止并发访问库存,适用于并发较低的场景。乐观锁:通过版本号检查来避免超卖,适用于高并发但能容忍少量失败的场景。分布式锁:在分布式系统中使用 Redis 等分布式锁来同步库存操作,确保跨服务的一致性。每种方法都有其适用的场景,选择合适的技术方案可以有效地避免超卖问题,保障系统的稳定性和一致性。

AAAcwaab的博客 2174

42、Windows Server Update Services(WSUS)全面指南

本文全面介绍了Windows Server Update Services(WSUS)的安装、配置、管理与优化策略。内容涵盖WSUS基础认知、客户端更新问题解决、多服务器架构设计、计算机分组策略、更新审批与报告查看,并深入探讨了SSL加密配置、性能优化、与其他系统(如AD和SCCM)集成以及常见故障排查方法。通过本指南,IT管理员可高效部署和管理企业级软件更新,提升安全性和运维效率。

cicd6pipeline的博客 98

redis如何解决秒杀超卖问题

1、使用reids的 watch + multi 指令实现 watch+multi解决超卖问题 #! /usr/bin/env python # -*- coding: utf-8 -*- import redis def sale(rs): while True: with rs.pipeline() as p: try: ...

hsw的博客 3662

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

超卖问题及其解决方法

超卖问题介绍 在高并发的场景下,比如商城秒杀活动中,一件商品的销售数量>库存数量的问题,称为超卖问题。主要原因是在高并发场景下,大量请求几乎同时到达,对库存资源进行竞争,由于没有适当的并发控制策略导致的错误。 简单的下单操作 如果没有并发的控制策略,则在下单时只需要考虑如下几步:查询库存数量、判断是否满足订单数量需求,提示库存不足或者减库存下单成功。通常我们会按照如下写法 public Serve...

E_N_T_J的博客 1万+

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Redis解决实现秒杀+解决超卖问题

利用hash结构替代string结构(记录获取锁的线程和重入次数),hash key-field-value,多存入一个value字段,用来记录获取锁的次数,所以当某一线程进行业务操作时(不止需要一次锁),可以多次获取锁,只有当value= 0时,锁才会被删除。不难看出当线程一获取锁业务阻塞后,此时锁超时自动释放锁(锁被误删),而线程一继续执行业务时,会继续释放锁从而删除其他线程的锁,因为我们的key值为 业务名+ “order:userId”,所以会出现并行情况,造成线程安全问题。

m0_74927596的博客 2933

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

超卖问题复现及一种解决方法

在用户订购权益、订购不可叠加的流量包等应用场景下,需求方经常会提出同一个商品只能向同一个用户销售一次,且库存不能小于0(即不能发生超卖),而超卖是开发中经常遇到的问题,本文通过实验将超卖问题复现,并给出超卖的一种解决方案,经验证有效。 1、复现 看下列代码,逻辑上无问题。 if (user == null){ return Result.error(CodeMsg.SESSION_ERRO...

HIT的专栏 1万+

超卖问题的解决方法

超卖问题,事务

lichunyu7199的博客 897

如何解决高并发下的超卖问题?

由秒杀引发的一个问题 秒杀最大的一个问题就是解决超卖的问题。其中一种解决超卖如下方式: update goods set num = num - 1 WHERE id = 1001 and num > 0 我们假设现在商品只剩下一件了,此时数据库中 num = 1; 但有100个线程同时读取到了这个 num = 1,所以100个线程都开始减库存了。 但你会最终会发觉,其实只有一个线程减...

- 3675

php下用redis解决秒杀超卖问题

秒杀超卖问题,就是有一个商品抢购活动,一个商品假如有100件库存,但是在抢购时有200人来抢购,这时就会并发,原本只有100的库存但是抢购的人过多,就会发生数据库里原本只有100的库存但是库存为0的时候还会有人提交成功,这就是超卖。 今天简单的用redis的队列来解决超卖问题。因为redis有list类型,list类型其实就是一个双向链表。通过push,pop操作从链表的头部或者尾部添加...

Srodong的博客 6188

超卖问题解决方案

在本项目的抢卷模块中需要实现下边两个需求。

hycccccch的博客 1439

php redis超卖,PHP用redis解决超卖的问题

原标题:PHP用redis解决超卖的问题前言在商品秒杀活动中,比如商品库存只有100,但是在抢购活动中可能有200人同时抢购,这样就出现了并发,在100件商品下单完成库存为0了还有可能继续下单成功,就出现了超卖。为了解决这个问题,今天我主要讲一下用redis队列的方式处理。redis有list类型,list类型其实就是一个双向链表。通过lpush,pop操作从链表的头部或者尾部添加删除元素。这使得...

weixin_32546235的博客 980

PHP用redis解决超卖的问题

前言在商品秒杀活动中,比如商品库存只有100,但是在抢购活动中可能有200人同时抢购,这样就出现了并发,在100件商品下单完成库存为0了还有可能继续下单成功,就出现了超卖...

代码技巧 1427

PHP如何解决超卖问题

但是,需要注意的是,引入锁机制可能会增加系统的复杂性和开销,因此需要权衡利弊来选择是否使用。在某些情况下,即使系统能够处理并发请求,但是由于处理逻辑存在缺陷,也可能导致超卖现象的发生。例如,在减库存的过程中,如果没有做好事务处理或者锁机制,就可能出现多个请求同时修改库存数据的情况,从而导致超卖。当多个用户同时尝试购买同一件商品时,如果系统的并发处理能力不足,或者处理逻辑存在缺陷,就可能导致超卖现象的发生。按照这个推论来分析,通过消息队列的缓冲作用,还可以平滑地处理突发的流量高峰,防止系统过载。

2401_85976667的博客 598

【Redis】库存超卖问题解决

一、问题分析 出现超卖问题一般都是线程安全引起的问题。正常情况下,是先查询库存,如果库存小于等于0就购买失败,接着第二个线程查询库存,如果库存小于等于0就购买失败。时序图如下所示 if (voucher.getStock() < 1) { // 库存不足 return Result.fail("库存不足!"); } //5,扣减库存 boolean success = seckillVoucherService.update() .setSql("stock= sto

菜鸟的博客 1431
上一篇: 解决缓存击穿问题
下一篇: Redis的持久化
小李不秃头♛
博客等级 码龄2年 619粉丝 102原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值