利用Redis increment 的原子操作,保障库存数平安
- 先查问redis中是否有库存信息,如果没有就去数据库查,这样就能够缩小拜访数据库的次数。
获取到后把数值填入redis,以商品id为key,数量为value。
留神要设置序列化形式为StringRedisSerializer,不然不能把value做加减操作。
还须要设置redis对应这个key的超时工夫,以防所有商品库存数据都在redis中。 - 比拟下单数量的大小,如果够就做后续逻辑。
- 执行redis客户端的increment,参数为正数,则做减法。因为redis是单线程解决,并且因为increment让key对应的value 缩小后返回的是批改后的值。
有的人会不做第一步查问间接减,其实这样不太好,因为当库存为1时,很多做减3,或者减30状况,其实都是不够,这样就白减。 - 扣减数据库的库存,这个时候就不须要再select查问,间接乐观锁update,把库存字段值减1 。
- 做完扣库存就在订单零碎做下单。
样例场景:
- 假如两个用户在第一步查问失去库存等于10,A用户走到第二步扣10件,同时一秒内B用户走到第二部扣3件。
- 因为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队列模式,用户触发购买商品后,进入队列,让用户的页面始终在转圈圈,等轮到他买的时候再进入结算页面,结算页面的后续流程和本文统一。