利用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队列模式,用户触发购买商品后,进入队列,让用户的页面始终在转圈圈,等轮到他买的时候再进入结算页面,结算页面的后续流程和本文统一。