乐趣区

关于电商:高并发电商扣库存是如何做的

利用 Redis increment 的原子操作,保障库存数平安

  1. 先查问 redis 中是否有库存信息,如果没有就去数据库查,这样就能够缩小拜访数据库的次数。
    获取到后把数值填入 redis,以商品 id 为 key,数量为 value。
    留神要设置 序列化形式为 StringRedisSerializer,不然不能把 value 做加减操作。
    还须要设置 redis 对应这个 key 的超时工夫,以防所有商品库存数据都在 redis 中。
  2. 比拟下单数量的大小,如果够就做后续逻辑。
  3. 执行 redis 客户端的 increment,参数为正数,则做减法。因为 redis 是 单线程解决 ,并且因为increment 让 key 对应的 value 缩小后返回的是批改后的值
    有的人会不做第一步查问间接减,其实这样不太好,因为当库存为 1 时,很多做减 3,或者减 30 状况,其实都是不够,这样就白减。
  4. 扣减数据库的库存,这个时候就不须要再 select 查问,间接乐观锁 update,把库存字段值减 1。
  5. 做完扣库存就在订单零碎做下单。

样例场景:

  1. 假如两个用户在第一步查问失去库存等于 10,A 用户走到第二步扣 10 件,同时一秒内 B 用户走到第二部扣 3 件。
  2. 因为 redis 单线程解决,若 A 用户线程先执行 redis 语句,那么当初库存等于 0,B 就只能失败,就不会出更新数据库了。
    public void order(OrderReq req) {String key = "product:" + req.getProductId();
        // 第一步:先查看 库存是否短缺
        Integer num = (Integer) redisTemplate.get(key);
          if (num == null){
          // 去查数据库的数据
          // 并且把数据库的库存 set 进 redis,留神应用 NX 参数示意只有当没有 redis 中没有这个 key 的时候才 set 库存数量到 redis
          // 留神要设置序列化形式为 StringRedisSerializer,不然不能把 value 做加减操作
          // 同时设置超时工夫,因为不能让 redis 存着所有商品的库存数,免得占用内存。if (count >=0) {
            // 设置有效期十分钟
            redisTemplate.expire(key, 60*10+ 随机数避免雪崩, TimeUnit.SECONDS);
        }
          // 缩小常常拜访数据库,因为磁盘比内存访问速度要慢
        }
        if (num < req.getNum()) {logger.info("库存有余");
        }
        // 第二步:缩小库存
        long value = redisTemplate.increment(key, -req.getNum().longValue());
        // 库存短缺
        if (value >= 0) {logger.info("胜利购买");
            // update 数据库中商品库存和订单零碎下单,单的状态未待领取
            // 离开两个零碎解决时,能够用 LCN 做分布式事务,然而也是有概率会订单零碎的网络超时
            // 也能够应用最终一致性的形式,更新库存胜利后,发送 mq,期待订单创立生成回调。boolean res= updateProduct(req);
              if (res)
                createOrder(req);
        } else {
            // 减了后小小于 0,如两个人同时买这个商品,导致 A 人第一步时看到还有 10 个库存,然而 B 人买 9 个先解决完逻辑,// 导致 B 人的线程 10-9=1, A 人的线程 1 -10=-9,则当初须要减少刚刚减去的库存,让他人能够买 1 个
            redisTemplate.increment(key, req.getNum().longValue());
            logger.info("复原 redis 库存");
        }
    }

update 应用乐观锁

updateProduct 办法中执行的 sql 如下:

update Product set count = count - #{购买数量} where id = #{id} and count - #{购买数量} >= 0;

尽管 redis 曾经避免了超卖,然而数据库层面,为了也要避免超卖,以防 redis 解体时无奈应用或者不须要 redis 解决时,则用乐观锁,因为不肯定全副商品都用 redis。

利用 sql 每条单条语句都是有事务的,所以两条 sql 同时执行,也就只会有其中一条 sql 先执行胜利,另外一条后执行,也如上文提及到的场景一样。

简略说一下分布式事务:

离开两个零碎解决库存和订单时,这个时候能够用 LCN 框架做分布式事务,然而因为是 http 申请的,也是有概率会订单零碎的网络超时,导致未返回后果。

其实也能够应用最终一致性的形式,数据表记录一条交互流水记录,更新库存胜利后,更新这个交互流水记录的库存操作字段为已解决,订单解决字段为解决中,而后发送 mq,期待订单创立生成回调。也要做定时工作做被动查问订单零碎的后果,以防没有后果回来。

计划劣势

  • 不须要频繁拜访数据库商品库存还有多少
  • 不阻塞其余用户
  • 平安扣减库存量
  • 内存拜访库存数量,缩小数据库交互

高并发额定优化

  • 用户拜访下单是,前端 ui 能够让用户触发结算后,把按钮置灰色,避免反复触发。
  • 能够依照库存数量来选定是否要用 redis,因为如果库存数量少,或者说最近下单次数少的商品,就不必放 redis,因为少人看和买的状况下,不用放 redis 导致占用内存。
  • 如果到工夫点抢购时,能够应用 mq 队列模式,用户触发购买商品后,进入队列,让用户的页面始终在转圈圈,等轮到他买的时候再进入结算页面,结算页面的后续流程和本文统一。
退出移动版