当前位置: 首页 > news >正文

高并发商城 商品为了防止超卖,都做了哪些努力?

在高并发商城场景中,防止商品超卖(即实际卖出量 > 商品库存总量)是核心技术挑战之一,需要从 数据库层、缓存层、应用层、分布式协调 等多个维度构建防御体系,结合业务场景设计多层防护。以下是实际业务中常用的技术方案和努力方向:

一、数据库层:保证库存操作的原子性(最核心防线)

数据库是库存数据的最终存储载体,其操作的原子性是防止超卖的基础。

1. 行锁 + 条件更新(悲观锁思路)

通过 UPDATE 语句的 WHERE 条件同时检查库存,利用数据库行锁保证操作的原子性,这是最直接有效的方案:

-- 扣减库存时,必须带库存校验条件
UPDATE product_stock 
SET stock = stock - 1, updated_at = NOW() 
WHERE product_id = ? AND stock >= 1; -- 核心:只有库存足够时才执行扣减
  • 原理:数据库会对匹配 product_id 的行加排他锁(X锁),同一时间只有一个事务能执行该更新,其他事务需等待锁释放。
  • 优势:严格保证库存不会超卖,适合库存精度要求极高的场景(如限量商品、奢侈品)。
  • 注意:需确保 product_id 有索引,避免行锁升级为表锁;控制事务长度,减少锁持有时间(如将非核心操作移到事务外)。

2. 乐观锁(版本号/库存字段校验)

适合 读多写少、并发冲突低 的场景,通过版本号或库存字段本身实现无锁化更新,减少锁竞争:

-- 方案1:用版本号(推荐)
UPDATE product_stock 
SET stock = stock - 1, version = version + 1, updated_at = NOW() 
WHERE product_id = ? AND version = ? AND stock >= 1;-- 方案2:用库存字段本身(适合简单场景)
UPDATE product_stock 
SET stock = stock - 1, updated_at = NOW() 
WHERE product_id = ? AND stock = ?; -- 扣减前先查询当前库存,作为条件
  • 原理:更新时校验“扣减前的库存状态”,若期间库存被其他事务修改(版本号变化或库存值变化),则更新失败(返回行数为0),应用层需重试或返回失败。
  • 优势:无锁竞争,吞吐量高,适合普通商品日常销售。
  • 注意:高并发下可能出现多次重试失败,需设置合理重试次数(如3次),避免无限重试导致性能下降。

3. 库存预扣减 + 事务补偿

针对 秒杀、大促 等场景,提前将库存从“可用库存”移到“预占库存”,后续再确认或释放:

-- 1. 预占库存(下单时)
UPDATE product_stock 
SET available_stock = available_stock - 1, reserved_stock = reserved_stock + 1 
WHERE product_id = ? AND available_stock >= 1;-- 2. 确认扣减(支付成功后)
UPDATE product_stock 
SET reserved_stock = reserved_stock - 1, sold_stock = sold_stock + 1 
WHERE product_id = ? AND reserved_stock >= 1;-- 3. 释放预占(超时未支付)
UPDATE product_stock 
SET available_stock = available_stock + 1, reserved_stock = reserved_stock - 1 
WHERE product_id = ? AND reserved_stock >= 1;
  • 原理:通过“可用库存→预占库存→已售库存”的状态流转,将“下单”和“最终扣减”分离,避免用户未支付却占用库存导致的超卖风险。
  • 优势:结合定时任务(如XX分钟未支付自动释放),灵活应对下单后未支付的场景,提升库存利用率。

二、缓存层:减轻数据库压力,前置拦截无效请求

高并发场景下(如秒杀),直接操作数据库会导致性能瓶颈,需用缓存(如Redis)前置处理库存请求。

1. Redis预存库存 + 原子扣减

将商品库存提前加载到Redis,下单时先在Redis中扣减,成功后再异步同步到数据库:

// 1. 初始化:将数据库库存加载到Redis
redisTemplate.opsForValue().set("stock:" + productId, initialStock);// 2. 扣减库存:用Redis的decr原子操作
Long remainStock = redisTemplate.opsForValue().decrement("stock:" + productId);
if (remainStock != null && remainStock >= 0) {// Redis扣减成功,发送消息到MQ,异步同步到数据库mqTemplate.send("stock-sync-topic", productId);return "下单成功";
} else {// Redis扣减失败(库存不足),直接返回return "库存不足";
}
  • 原理:Redis的 decrement 是原子操作,同一时间只有一个请求能成功扣减,避免并发冲突;数据库同步通过消息队列异步执行,不阻塞主流程。
  • 优势:Redis性能远高于数据库,能支撑每秒10万级的并发请求,适合秒杀等高流量场景。
  • 注意:需处理“Redis与数据库一致性”问题(如Redis扣减后数据库同步失败,可通过定时任务校验修复);防止Redis宕机导致库存丢失(需持久化配置+主从架构)。

2. 缓存预热 + 库存隔离
  • 缓存预热:大促前将商品库存提前加载到Redis,避免高峰期缓存穿透(大量请求直接打到数据库)。
  • 库存隔离:将“秒杀库存”和“日常库存”在缓存中分开存储(如 seckill:stock:1001normal:stock:1001),避免秒杀流量影响日常销售。

三、应用层:控制流量,减少无效竞争

通过限流、排队等手段,从源头减少并发请求对库存系统的冲击。

1. 接口限流(防止流量过载)
  • 前端限流:按钮置灰、倒计时,避免用户重复点击;
  • 后端限流:用令牌桶/漏桶算法(如Guava RateLimiter、Sentinel)限制接口QPS,超出部分直接返回“系统繁忙”:
// Sentinel限流示例:限制/product/seckill接口每秒最多1000请求
@SentinelResource(value = "seckill", blockHandler = "handleSeckillBlock")
@PostMapping("/product/seckill")
public Result seckill(...) { ... }// 限流降级处理
public Result handleSeckillBlock(...) {return Result.error("请求过于频繁,请稍后再试");
}

2. 队列化处理(将并发转为串行)

用消息队列(如RabbitMQ、Kafka)将所有下单请求排队,由消费者按顺序处理,避免同时操作库存:

// 1. 接收下单请求,直接放入队列
@PostMapping("/order/submit")
public Result submitOrder(OrderDTO dto) {mqTemplate.send("order-queue", dto); // 放入队列,立即返回“排队中”return Result.success("已进入排队,请等待结果");
}// 2. 消费者按顺序处理队列消息(单线程或固定线程池)
@RabbitListener(queues = "order-queue")
public void processOrder(OrderDTO dto) {// 此处执行库存扣减、下单逻辑(串行处理,无并发冲突)reduceStock(dto.getProductId());createOrder(dto);
}
  • 原理:通过队列的FIFO特性,将高并发请求转为串行处理,库存操作天然有序,避免超卖。
  • 优势:实现简单,适合秒杀等“流量集中但总量可控”的场景(如1万件商品,只需处理1万条队列消息)。

四、分布式协调:跨服务/跨节点的库存一致性

在分布式系统(多服务实例、多数据库节点)中,需保证不同节点对库存的操作一致。

1. 分布式锁(控制跨节点并发)

用Redis的 SETNX 或ZooKeeper的临时节点实现分布式锁,确保同一商品在多节点间只有一个操作能执行:

// Redis分布式锁示例
String lockKey = "lock:stock:" + productId;
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {try {// 获得锁,执行库存扣减reduceStock(productId);} finally {// 释放锁redisTemplate.delete(lockKey);}
} else {// 未获得锁,重试或返回失败return "系统繁忙,请重试";
}
  • 注意:需设置锁超时时间,避免服务宕机导致锁永久持有;推荐用Redisson等成熟框架,处理锁自动续期、重入等问题。

2. 最终一致性方案(柔性事务)

对于非核心商品(允许短暂不一致,最终一致),可采用TCC(Try-Confirm-Cancel)或SAGA模式:

  • TCC:Try阶段预扣减库存,Confirm阶段确认扣减,Cancel阶段回滚预扣减;
  • SAGA:将库存扣减拆分为“扣减→补偿”步骤,若扣减失败,自动执行补偿操作(如恢复库存)。

五、监控与兜底:发现并修复异常

即使多层防护,仍可能因极端情况(如网络分区、缓存穿透)导致库存异常,需通过监控和兜底方案补救:

  1. 实时监控:监控库存字段(available_stocksold_stock)的差值,若 sold_stock > 初始库存,立即告警;
  2. 定时校验:定时比对Redis库存与数据库库存,发现不一致时自动修复(以数据库为准);
  3. 人工兜底:大促期间安排运维人员值守,异常时手动冻结商品、调整库存。

总结:多层防护的核心逻辑

高并发商城防超卖的本质是 “在性能与一致性之间找平衡”,不同业务场景选择不同方案组合:

  • 秒杀/限量商品:Redis原子扣减 + 数据库行锁 + 队列化 + 限流(优先保证一致性和抗流量能力);
  • 日常销售商品:数据库乐观锁 + 缓存预热(优先保证吞吐量);
  • 分布式场景:分布式锁 + 最终一致性方案(保证跨节点一致性)。

没有“银弹”方案,需结合业务量级、库存精度要求、技术成本综合设计,核心原则是“多层防护,层层兜底”。

http://www.dtcms.com/a/366802.html

相关文章:

  • 2025国赛C题保姆级教程思路分析 NIPT 的时点选择与胎儿的异常判定
  • Spring Cloud Alibaba快速入门01
  • C语言结构体:轻松管理球员数据
  • SpringMVC的异常处理和拦截器
  • 【C语言】深入理解指针(4)
  • nextcyber——常见应用攻击
  • 一个老工程师的“新生”:良策金宝AI,让我重新爱上设计
  • [光学原理与应用-389]:设计 - 深紫外皮秒脉冲激光器 - 元件 - 1064nm种子光准直透镜
  • 2025年经管领域专业资格认证发展路径分析
  • 数据结构 之 【模拟实现哈希表】
  • Python 值传递 (Pass by Value) 和引用传递 (Pass by Reference)
  • 电池预测 | 第36讲 Matlab基于CNN-BiGRU-Attention的锂电池剩余寿命预测
  • JVM 运行时数据区域
  • 开源本地LLM推理引擎(Cortex AI)
  • 【PZ-AU15P】璞致fpga开发板 Aritx UltraScalePlus PZ-AU15P 核心板与开发板用户手册
  • ZooKeeper核心ZAB选举核心逻辑(大白话版)
  • 性能堪比claude sonnet4,免费无限使用!claude code+魔搭GLM4.5在ubuntu上安装完整流程
  • 三高项目-缓存设计
  • SQL常见索引失效导致慢查询情况
  • Java 双亲委派机制解析和破坏双亲委派的方式
  • T检验(pearman)
  • 【全网最全】《2025国赛/高教杯》C题 思路+代码python和matlab+文献 一到四问 退火算法+遗传算法 NIPT的时点选择与胎儿的异常判定
  • 电商金融贷款服务市场趋势与竞争分析
  • [frontend]WebGL是啥?
  • 鸿蒙NEXT交互机制解析:从输入设备到手势响应的全面指南
  • Node.js 18+安装及Claude国内镜像使用、idea中claude插件下载指南
  • 【AI论文】UI-TARS-2技术报告:借助多轮强化学习推进图形用户界面(GUI)智能体发展
  • Django事务
  • 《Docker 零基础入门到实战:容器化部署如此简单,运维效率直接拉满》
  • 【有鹿机器人自述】我在社区的365天:扫地、卖萌、治愈人心