Redis高并发分布式锁详解( 六 )


案例演示场景说明:
在秒杀抢购的情况下,大量的秒杀商品其实都是走同一逻辑的,如果使用公用的锁必然是不合适的 , 这会大大阻塞住整个系统,而且不同商品之前根本不存在竞争关系,故一般我们会采用类似 redis_promotion_product_stock_$productId :1000 这种设置库存值。那么对于每个商品既然拥有了自己的库存那么对于对应库存加锁就能缩小了锁的颗粒度 。
但是这种真的就可行了嘛?对A商品的下单,都必对"redis_promotion_product_lock_A"这个锁key来加锁 。这样会导致对同一个商品的下单请求,就必须串行化,一个接一个的处理 。假设加锁之后,释放锁之前,查库存 -> 创建订单 -> 扣减库存,这个过程性能很高吧,算他全过程20毫秒 , 这应该不错了 。那么1秒是1000毫秒,只能容纳50个对这个商品的请求依次串行完成处理 。这种性能远远不能满足我们想要的 。而且对于变量进行原子操作这种:
13. 【参考】volatile 解决多线程内存不可见问题 。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题 。如果是 count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数) 。了解过源码的都应该知道 AtomicLong和ConcurrentHashMap 都是优化过类似操作的 。那么为何不参考呢?【分段加锁思想
AtomicLong将变量base结合一些数组变量,共同维持总数 。面对高并发下,是针对单个数组节点进行加锁,修改节点内数据,而总量依旧是他们加起来,而且数组的最大容量与核心数有关 。是不是豁然开朗?这与我们的场景是不是很像 。多台服务器对应多核心 。假设有4台服务器,我们是不是可以将变量 redis_promotion_product_stock_$productId :1000 拆解为 redis_promotion_product_stock_$productId_1 :250,..,redis_promotion_product_stock_$productId_4 :250,
这样的4份(性能好的服务器,可以适当偏多) 。那么服务器的CPU是不是就充分利用了 , 而且他们之前的并发问题是不是变小了 。
又或者分成10份,每个服务器持有一份,用完再去获取新的份额(这种需要额外添加列表维护,但是并发冲突再次下降) 。
一旦对某个数据做了分段处理之后,就会存在一个问题:假设服务器A的份额消耗完了,但是其余服务器还存于份额:
解决方案(处理库存不足的方案是必须要做的):
1.发现这个分段库存里的库存不足了 , 释放锁,然后立马换下一个分段库存 , 再次尝试加锁后尝试处理(核心逻辑) 。
     2.依托于负载均衡,先判断总库存是否还是有的,有的负载到其他服务器,要设置好重试次数 。(或者不重试,返回友好提示,让客户自己去重试 , 毕竟秒杀抢购这东西)
总结1.分布式锁并发优化,是一个十分复杂的过程 , 需要考虑数据的拆分,如何选择拆分的数据,如何校验,如何切换等等 。这些都是需要我们考量和积累经验的 。

推荐阅读