扣减库存(高并发更新数据库都可使用)

扣减库存(高并发更新数据库都可使用)

​ 对于“秒杀”活动,一般公司都是不允许商品超卖的,例如我司短信平台不允许客户超额消费。一旦超出即会造成损失。如果被恶意流量利用,则损失巨大。

  1. 扣减的方式

    为了不超卖,扣减常用以下三种方式:

    • 下单后扣减库存:这种扣减方式最简单,也最好理解,但是存在用户下单后不付款,特别是被恶意用户利用“秒杀器”大量抢购商品,但是不支付。如果是这种情况,那商家就无法达到真正目的,且用户无法得到对应商品。

    • 支付后扣减库存:在用户付款玩后再扣减库存,这样能解决上面方案中的“下单不支付”的情况。但是这种方式容易出现超卖的问题。因为,在瞬时流量高峰下,会有很多用户付款成功,但只有一小部分能真正抢到商品,大部分用户在付款后系统会提示“已售罄”或“活动结束”。

      注意:但是此种方式,可以通过上架补货完成业务。

    • 预扣减库存:用户下完订单后,系统会为其锁定库存一段时间,例如30分钟;在超过锁定时间后自动释放库存让其他用户抢购。当用户付款时,系统会校验库存是否在锁定有效期内,如果在有效期内,则可以进行支付;如果有效期已过,则重新锁定库存,若锁定失败则提醒库存不足。

      用户下单后,有些平台会有一个支付倒计时,例如12306,这也与预扣减方式相同。但此种方式也存在恶意用户占用有效时间的可能。

  2. 在高并发的情况下,如何扣减库存**

    在我们工作中根据实际业务去选择以上三种方式,目前最强用的就是第三种方式。

    对于上述第一种扣减方式,利用数据库的事务特性,可以保证订单和库存扣减数量的一致性。

    1. 先将商品数量查询出来

      例如:

      select num from table where pro_id = #{pro_id}
      
    2. 更新库存

      用“库存数量”减去“购买的数量”,然后将结果值更新到数据库中。例如原本数量为10,用户买了2个,则库存为8

      update table set num = 8 where pro_id = #{pro_id}
      

      注意:为什么是更新“结果值”的操作,而不是直接更新“扣减”的操作。

      因为,“扣减”操作不是幂等的,如果接口设计的不够完美,没有考虑幂等性,那么在由于网络原因或者其他原因造成重试之后,会出现重复“扣减”,导致“超卖”,甚至库存为负数的情况。

      通过以上处理,基本可以保证下单扣减库存的准确性,但是对于“秒杀”,依然存在风险,例如两个用户同时抢购,都拿到了库存数量为10的商品,其中一个用户购买了5件商品,随后更新库存数量为5件;领一个用户请求购买了3件商品,随后更新库存数量为7件,则会出现并发更新数据不一致的情况。

      所以在更新库存数量时,要将“当前库存数量”与“之前库存数量”进行比对,例如:

      update table set num = #{new_num} where pro_id = #{pro_id} and num = #{old_num}
      

      有了以上这种比对,在并发更新时,两个用户只有在更新提交前查询到库存为10的用户才能成功更新库存数量。

  3. 高并发的情况下,优化库存数量扣减

    通过流量的分层控制,可以管控大量的“读”请求。但是依然会有很大的流量进入真正的下单逻辑。对于这么大的流量,除去上述说的数据库隔离外,还需要进一步优化,否则数据库的读/写性能依然是系统的瓶颈。

    优化操作:

    1. 利用好缓存

      如果只是简单的一个扣减库存的操作,可以先将库存数量直接放到缓存中,然后利用分布式缓存,例如大名鼎鼎的Redis,去应对这种高并发情况下的系统挑战。

      注意:使用缓存也是存在一定风险的,比如:缓存节点出现了异常,库存如何处理?使用缓存不仅需要考虑分布式缓存高可用,还要考虑各种限流容错机制,以确保分布式缓存对外提供服务。

    2. 异步处理

      如果是复杂的扣减库存操作(比如涉及信息校验或牵连其他模块),则建议数据库直接进行数量的扣减,可以使用异步的方式来应对。

      • 在用户下单后,不立即生成订单,而是将所有订单依次放入队列。
      • 下单模块依据自身的处理速度,从队列中依次获取订单进行扣减操作。
      • 在订单生成后,用户即可进行支付操作。

      这种方式是针对“秒杀”这种场景的,依据先到先得的原则来保证公平公正,所有用户都可以抢购,而且对于用户来说体验感也不会很差。

      注意:对于用户来说只是在抢购时有一次提交抢购请求,之后就是等待程序处理进度。这个时间不可以太久,不管成功或者失败都要及时给用户反馈,要不然用户体验感极差。

好的,这个问题可以回答。对于高并发订单扣减库存的问题,需要考虑多线程并发和数据库事务的管理。以下是一个示例代码: ```java public class OrderService { private final InventoryDao inventoryDao; public OrderService(InventoryDao inventoryDao) { this.inventoryDao = inventoryDao; } // 扣减库存,返回值为扣减是否成功 public boolean reduceStock(int itemId, int quantity) { int stock = inventoryDao.getStock(itemId); if (stock < quantity) { return false; } return inventoryDao.reduceStock(itemId, quantity); } } public class InventoryDao { // 获取库存 public int getStock(int itemId) { String sql = "SELECT quantity FROM inventory WHERE item_id = ?"; try (Connection conn = getConnection(); PreparedStatement statement = conn.prepareStatement(sql)) { statement.setInt(1, itemId); try (ResultSet rs = statement.executeQuery()) { if (rs.next()) { return rs.getInt("quantity"); } } } catch (SQLException e) { throw new RuntimeException(e); } return 0; } // 扣减库存,返回值为扣减是否成功 public boolean reduceStock(int itemId, int quantity) { String sql = "UPDATE inventory SET quantity = quantity - ? WHERE item_id = ?"; try (Connection conn = getConnection()) { conn.setAutoCommit(false); try (PreparedStatement statement = conn.prepareStatement(sql)) { statement.setInt(1, quantity); statement.setInt(2, itemId); int affectedRows = statement.executeUpdate(); if (affectedRows == 1) { conn.commit(); return true; } } conn.rollback(); } catch (SQLException e) { throw new RuntimeException(e); } return false; } private Connection getConnection() throws SQLException { // 获取数据库连接 } } ``` 在这个示例代码中,OrderService 是对外提供的服务类,它依赖于 InventoryDao 来管理库存。reduceStock 方法是扣减库存的核心方法,它首先从数据库中获取当前库存数量,然后检查是否足够扣减。如果足够扣减,就开启一个数据库事务,更新库存数量,最后提交事务。如果不足够扣减,就直接返回 false 表示扣减失败。 在实际使用中,需要根据具体场景进行优化,比如使用连接池来提高数据库连接的性能,采用分布式锁来控制并发等等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值