面试案例-每秒上千订单场景,如何优化分布式锁的并发能力

场景引入

假如下单时,用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景?

库存超卖怎么产生的

先看下如果不用分布式锁,所谓的电商库存超卖是啥意思?如下图:

库存超卖产生缘由

假设订单系统部署两台机器上,不同的用户都要同时买10台iphone,分别发了一个请求给订单系统。接着每个订单系统实例都去数据库里查了一下,当前iphone库存是12台。于是乎,每个订单系统实例都发送SQL到数据库里下单,然后扣减了10个库存,其中一个将库存从12台扣减为2台,另外一个将库存从2台扣减为-8台。这就产生了库存超卖。

分布式锁解决库存超卖

先看看分布式锁的实现原理:
同一个锁key,同一时间只能有一个客户端拿到锁,其他客户端会陷入无限的等待来尝试获取那个锁,只有获取到锁的客户端才能执行下面的业务逻辑。

分布式锁伪代码

代码大概就是上面那个样子,现在分析一下,为啥这样做可以避免库存超卖?

分布式锁解决库存超卖

从上图可以看到,只有一个订单系统实例可以成功加分布式锁,然后只有他一个实例可以查库存、判断库存是否充足、下单扣减库存,接着释放锁。

释放锁之后,另外一个订单系统实例才能加锁,接着查库存,一下发现库存只有2台了,库存不足,无法购买,下单失败。不会将库存扣减为-8的。

其他方案解决库存超卖

可以使用比如悲观锁,分布式锁,乐观锁,队列串行化,异步队列分散,Redis原子操作,等等方案。

分布式锁的高并发场景

现在看下分布式锁的方案在高并发场景下有什么问题?

问题很大!分布式锁一旦加了之后,对同一个商品的下单请求,会导致所有客户端都必须对同一个商品的库存锁key进行加锁。

比如,对iphone这个商品的下单,都必对“iphone_stock”这个锁key来加锁。这样会导致对同一个商品的下单请求,就必须串行化,一个接一个的处理。

假设加锁之后,释放锁之前,查库存 → 创建订单 → 扣减库存,这个过程性能很高吧,算他全过程20毫秒,那么1秒是1000毫秒,只能容纳50个对这个商品的请求依次串行完成处理。

比如一秒钟来50个请求,都是对iphone下单的,那么每个请求处理20毫秒,一个一个来,最后1000毫秒正好处理完50个请求。如下图:

分布式锁串行处理

所以,简单的使用分布式锁来处理库存超卖问题,存在的缺陷就是同一个商品多用户同时下单的时候,会基于分布式锁串行化处理,导致没法同时处理同一个商品的大量下单的请求。

如何优化分布式锁的高并发能力

其实也很简单,相信很多人看过java里的ConcurrentHashMap的源码和底层原理,应该知道里面的核心思路,就是分段加锁!

把数据分成很多个段,每个段是一个单独的锁,所以多个线程过来并发修改数据的时候,可以并发的修改不同段的数据。不至于说,同一时间只能有一个线程独占修改ConcurrentHashMap中的数据。

另外,Java 8中新增了一个LongAdder类,也是针对Java 7以前的AtomicLong进行的优化,解决的是CAS类操作在高并发场景下,使用乐观锁思路,会导致大量线程长时间重复循环。

LongAdder中也是采用了类似的分段CAS操作,失败则自动迁移到下一个分段进行CAS的思路。

分布式锁的优化思路也是类似的,如下图:

优化分布式锁高并发能力

其实就是分段加锁。假如现在iphone有1000个库存,那么你完全可以给拆成20个库存段,要是你愿意,可以在数据库的表里建20个库存字段,比如stock_01,stock_02,类似这样的,也可以在redis之类的地方放20个库存key。

总之,就是把1000件库存给他拆开,每个库存段是50件库存,比如stock_01对应50件库存,stock_02对应50件库存。

接着,每秒1000个请求过来了,然后写一个简单的随机算法,每个请求都是随机在20个分段库存里,选择一个进行加锁。

这样就可以同时有最多20个下单请求一起执行,每个下单请求锁了一个库存分段,然后在业务逻辑里面,就对数据库或者是Redis中的那个分段库存进行操作即可,包括查库存 → 判断库存是否充足 → 扣减库存。

这相当于一个20毫秒,可以并发处理掉20个下单请求,那么1秒就可以依次处理掉20 X 50 = 1000个对iphone的下单请求了。

一旦对某个数据做了分段处理之后,有一个坑一定要注意:如果某个下单请求,咔嚓加锁,然后发现这个分段库存里的库存不足了,此时咋办?

这时得自动释放锁,然后立马换下一个分段库存,再次尝试加锁后尝试处理。这个过程一定要实现。

分布式锁并发优化方案的不足

最大的不足就是实现太复杂了。

  • 首先,得对一个数据分段存储,一个库存字段本来好好的,现在要分为20个分段库存字段;
  • 其次,在每次处理库存的时候,还得自己写随机算法,随机挑选一个分段来处理;
  • 最后,如果某个分段中的数据不足了,你还得自动切换到下一个分段数据去处理。

这个过程都是要手动写代码实现的,还是有点工作量,挺麻烦的。

本文标题:面试案例-每秒上千订单场景,如何优化分布式锁的并发能力

文章作者:王洪博

发布时间:2019年04月14日 - 16:04

最后更新:2019年12月03日 - 05:12

原始链接:http://whb1990.github.io/posts/ef094abf.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%