京东二面:商品购买过程中,库存的抵扣过程是怎样的?如何防止超卖?

本文探讨了商品购买过程中库存扣减的并发控制问题,涉及悲观锁、乐观锁方法,以及使用Redis进行库存操作的优缺点,强调了并发环境下的数据一致性与幂等性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点击关注公众号,Java 干货及时推送↓

推荐阅读:铜三铁四,怒拿 35K * 14 薪!


在商品购买的过程中,库存的抵扣过程,一般操作如下:

1、select根据商品id查询商品的库存。

2、根据下单的数量,计算库存是否足够,如果存库不足则抛出库存不足的异常,如果库存足够,则减去扣除的库存得到最新的库存剩余值。

3、set设置最新的库存剩余值。

上述过程的伪代码如下:

// 根据商品id获取商品剩余库存
select stock_remaing from stock_table where id=${goodsId};

// 操作库存
// 比较库存
if(stock_remaing <quantity){
   // 抛出库存不足的异常
}
else{
  // 抵扣以后的库存值
  int new_stock=stock_remaing - quantity;
}

// 根据商品id设置计算后的库存
update stock_table  set stock_remaing =${new_stock} id=${goodsId};

并发修改数据库存超卖

如果数据库事务的隔离级别不是串行化(serializable),根据事务的特性,在并发修改的时候,可能会出现写覆盖的问题。

假设,商品的剩余库存stock_remaing 为100,客户A下单20,客户B下单30,在并发扣库存的时候,可能存在超卖。如果客户A和客户B同时获取剩余库存为100,则会出现事务后提交的值会覆盖前一个客户提交的值,有可能剩余的库存是80或者70。

流程如下:

248b2bbcf071532b580efdab0a64ac9e.jpeg

加锁更新存库

为了在事务控制中,防止写覆盖,你会想到使用select for update的方式,将该商品的库存锁住,然后执行余下的操作。

流程如下:

ec6e059ff5a75168fb5c8ff986fbbf24.jpeg

以上,使用悲观锁方式,在分布式服务中,如果并发情况比较高的时候,扣减库存的操作是串行操作,效率很低。最新面试题整理好了,点击Java面试库小程序在线刷题。

使用乐观锁的方式更新

在更新的时候,使用(CAS+版本号更新)+重试条件(重试次数或者重试时间限制)乐观锁的方式更新库存。此时,如果,客户A和客户B同时读取到库存剩余100,在更新的时候,有一个操作会失败。

流程如下:

03325ccab8d72dd19e91b594660357be.jpeg

该种方式可以大大提高并发性,也可以保证数据的一致性;通过重试次数和重试时间的条件控制,可以防止过多的重试带来的数据库压力。

推荐一个 Spring Boot 基础教程及实战示例:

https://github.com/javastacks/spring-boot-best-practice

可以使用直接递减的方式执行么?

在抵扣库存的时候,有的人提议不执行select,计算,set三段式的操作,直接扣减的方式,并且对于扣减到小于零的情况作了判断。伪代码如下:

update stock_table set remaing_stock=remaing_stock-${quantity} 
where id =商品id
and remaing_stock>${quantity};

在分布式服务调用中,因为网络异常,获取服务器异常,可能在微服务调用时,存在服务重试。例如,场景的网关超时,服务重试机制。此时,该种方式不满足幂等性,而存在多扣的情况。例如,同一用户扣减库存时,服务重试,极端情况下,该用户扣减库存操作执行多次,则就出现了商品超卖。

可以使用redis进行库存的抵扣么?

答案是可以使用redis的事务性扣减余额,但在CAS机制上比mysql没有优势,高性能是因为其内存存储的原因,带来的副作用是数据有丢失风险。

原文链接:https://blog.csdn.net/new_com/article/details/105568124


推荐阅读:

铜三铁四,怒拿 35K * 14 薪!

自欺欺人,这简历一看就是包装的!

飞书大裁员,冒险说几句大实话。。

重要通知:我宣布永久免费!!

还有人在做 SSH 外包项目??

关注公众号学习最主流的 Java 技术↓

### 解决Eclipse JSP项目中 'javax.servlet.http.HttpServlet' 类未在Java构建路径上找到的问题 此问题通常发生在开发动态Web应用程序时,当项目的构建路径缺少必要的库或运行时环境配置不当时。以下是详细的分析和解决方案: #### 1. 配置服务器运行时环境 如果 `javax.servlet` API 的类无法被识别,则可能是因为尚未正确配置服务器运行时环境。可以通过以下方式解决问题[^4]: - 打开 **Window -> Preferences -> Server -> Runtime Environments**。 - 单击 **Add** 按钮,选择适合的 Apache Tomcat 版本或其他支持 Servlet 的应用服务器。 - 填写 Apache 或其他服务器的实际安装目录。 完成上述步骤后,重新启动 Eclipse 并验证错误是否消失。 #### 2. 添加服务器到项目属性 即使已经设置了服务器运行时环境,仍需将其关联至当前项目才能正常工作。具体操作如下: - 右键单击项目名称,选择 **Properties**。 - 转到 **Project Facets** 页面,启用 **Dynamic Web Module** 和相关依赖项(如 Java EE)[^3]。 - 切换到 **Targeted Runtimes** 页面,勾选之前添加的服务器实例。 通过这些设置可以确保项目能够访问所需的 Servlet 库。 #### 3. 使用Maven管理依赖关系 对于基于 Maven 构建的项目,应确认 pom.xml 文件已包含正确的 servlet-api 依赖声明。例如: ```xml <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> ``` 注意 `<scope>` 设置为 provided 表明该库由目标容器提供,在编译阶段可用但在部署时不打包进 WAR 文件[^5]。 #### 4. 清理并重建工程 有时缓存可能导致某些更改未能生效。执行清理命令有助于刷新状态: - 在菜单栏依次点击 Project -> Clean... ,然后选择对应的项目进行清除处理; - 如果仍然存在问题,可尝试删除 .metadata 文件夹下的 workspace 数据后再重试一次整个过程。 综上所述,按照以上方法逐一排查即可有效解决因缺失 HttpServlet 导致的一系列异常情况。 ### 示例代码片段 下面展示了一个简单的继承自 HttpServlet 的 Servlet 实现例子供参考: ```java import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/example") public class ExampleServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().append("Served at: ").append(request.getContextPath()); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值