当前位置: 首页 > 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.xdnf.cn/news/1451791.html

相关文章:

  • PostgreSQL18-FDW连接的 SCRAM 直通身份验证
  • 当便捷遇上复杂,低代码的路该怎么走?
  • Linux 基础IO-从 “一切皆文件” 到自定义 libc 缓冲区
  • fastmcp2.0的传输方式
  • DFT:从RL的视角修正SFT损失的权重
  • 【高分论文密码】大尺度空间模拟预测与数字制图
  • Django事务
  • Leetcode 240. 搜索二维矩阵 II 矩阵 / 二分
  • 垃圾回收,几种GC算法及GC机制
  • 数据库中事务、指令、写法解读
  • 搭建基于 Solon AI 的 Streamable MCP 服务并部署至阿里云百炼
  • 【多线程初阶】线程安全问题 死锁产生 何如避免死锁
  • 前端vue常见标签属性及作用解析
  • 零售消费企业的数字化增长实践,2025新版下载
  • 在 Debian 系统上清理缓存的方式和具体操作方法
  • Grafana - 监控磁盘使用率Variables使用
  • 卫星互联网安全风险及关键技术探索
  • 【深度学习】P1 引言(待完成)
  • Conda 常用命令大全
  • Axure RP 9 Mac 交互原型设计
  • iPhone17再爆猛料?苹果2025秋季发布会亮点抢先看
  • Jenkins调用ansible部署lnmp平台
  • 阿里云-基于通义灵码实现高效 AI 编码 | 1 | 在 Visual Studio Code 中安装和使用灵码
  • Redis vs Memcached vs MongoDB:深入对比与选型指南
  • AE(自动编码器)技术解析
  • Photoshop - Photoshop 触摸功能
  • 2025高教社杯国赛数学建模选题建议+初步分析
  • Java Web :技术根基与产业实践的多维耦合
  • CSS 渐变边框
  • tensorflow常用使用场景