明天给大家聊一个有意思的话题:每秒上千订单场景下,如何对分布式锁的并发能力进行优化?
背景引入
首先,咱们一起来看看这个问题的背景?
前段时间有个敌人在里面面试,而后有一天找我聊说:有一个国内不错的电商公司,面试官给他出了一个场景题:
如果下单时,用分布式锁来避免库存超卖,然而是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应答这个场景?
他说他过后没答上来,因为没做过没什么思路。其实我过后听到这个面试题心里也感觉有点意思,因为如果是我来面试候选人的话,应该会给的范畴更大一些。
比方让面试的同学聊一聊电商高并发秒杀场景下的库存超卖解决方案,各种计划的优缺点以及实际,进而聊到分布式锁这个话题。
因为库存超卖问题是有很多种技术解决方案的,比方乐观锁,分布式锁,乐观锁,队列串行化,Redis 原子操作,等等吧。
然而既然那个面试官兄弟限定死了用分布式锁来解决库存超卖,我预计就是想问一个点:在高并发场景下如何优化分布式锁的并发性能。
我感觉,面试官发问的角度还是能够承受的,因为在理论落地生产的时候,分布式锁这个货色保障了数据的准确性,然而他人造并发能力有点弱。
刚好我之前在本人我的项目的其余场景下,的确是做过高并发场景下的分布式锁优化计划,因而正好是借着这个敌人的面试题,把分布式锁的高并发优化思路,给大家来聊一聊。
库存超卖景象是怎么产生的?
先来看看如果不必分布式锁,所谓的电商库存超卖是啥意思?大家看看上面的图:
这个图,其实很清晰了,假如订单零碎部署两台机器上,不同的用户都要同时买 10 台 iphone,别离发了一个申请给订单零碎。接着每个订单零碎实例都去数据库里查了一下,以后 iphone 库存是 12 台。
俩大兄弟一看,乐了,12 台库存大于了要买的 10 台数量啊!于是乎,每个订单零碎实例都发送 SQL 到数据库里下单,而后扣减了 10 个库存,其中一个将库存从 12 台扣减为 2 台,另外一个将库存从 2 台扣减为 - 8 台。
当初完了,库存呈现了正数!泪奔啊,没有 20 台 iphone 发给两个用户啊!这可如何是好。
用分布式锁如何解决库存超卖问题?
咱们用分布式锁如何解决库存超卖问题呢?其实很简略,回顾一下上次咱们说的那个分布式锁的实现原理:
同一个锁 key,同一时间只能有一个客户端拿到锁,其余客户端会陷入有限的期待来尝试获取那个锁,只有获取到锁的客户端能力执行上面的业务逻辑。
代码大略就是下面那个样子,当初咱们来剖析一下,为啥这样做能够防止库存超卖?
大家能够顺着下面的那个步骤序号看一遍,马上就明确了。从上图能够看到,只有一个订单零碎实例能够胜利加分布式锁,而后只有他一个实例能够查库存、判断库存是否短缺、下单扣减库存,接着开释锁。
开释锁之后,另外一个订单零碎实例能力加锁,接着查库存,一下发现库存只有 2 台了,库存有余,无奈购买,下单失败。不会将库存扣减为 - 8 的。
有没有其余计划能够解决库存超卖问题?
当然有啊!比方乐观锁,分布式锁,乐观锁,队列串行化,异步队列扩散,Redis 原子操作,等等,很多计划,咱们对库存超卖有本人的一整套优化机制。
然而后面说过了,这篇文章就聊一个分布式锁的并发优化,不是聊库存超卖的解决方案,库存超卖只是一个业务场景而已。
分布式锁的计划在高并发场景下
好,当初咱们来看看,分布式锁的计划在高并发场景下有什么问题?
问题很大啊!兄弟,不晓得你看进去了没有。分布式锁一旦加了之后,对同一个商品的下单申请,会导致所有客户端都必须对同一个商品的库存锁 key 进行加锁。
比方,对 iphone 这个商品的下单,都必对“iphone_stock”这个锁 key 来加锁。这样会导致对同一个商品的下单申请,就必须串行化,一个接一个的解决。大家再回去对照下面的图重复看一下,应该能想明确这个问题。
假如加锁之后,开释锁之前,查库存 -> 创立订单 -> 扣减库存,这个过程性能很高吧,算他全过程 20 毫秒,这应该不错了。
那么 1 秒是 1000 毫秒,只能包容 50 个对这个商品的申请顺次串行实现解决。比方一秒钟来 50 个申请,都是对 iphone 下单的,那么每个申请解决 20 毫秒,一个一个来,最初 1000 毫秒正好解决完 50 个申请。
大家看一眼上面的图,加深一下感觉。
所以看到这里,大家起码也明确了,简略的应用分布式锁来解决库存超卖问题,存在什么缺点。
缺点就是同一个商品多用户同时下单的时候,会基于分布式锁串行化解决,导致没法同时解决同一个商品的大量下单的申请。
这种计划,要是应答那种低并发、无秒杀场景的一般小电商零碎,可能还能够承受。因为如果并发量很低,每秒就不到 10 个申请,没有刹时高并发秒杀单个商品的场景的话,其实也很少会对同一个商品在一秒内霎时下 1000 个订单,因为小电商零碎没那场景。
如何对分布式锁进行高并发优化?
好了,终于引入正题了,那么当初怎么办呢?
面试官说,我当初就卡死,库存超卖就是用分布式锁来解决,而且一秒对一个 iphone 下上千订单,怎么优化?
当初依照方才的计算,你一秒钟只能解决针对 iphone 的 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 个分段库存里,抉择一个进行加锁。
bingo!这样就好了,同时能够有最多 20 个下单申请一起执行,每个下单申请锁了一个库存分段,而后在业务逻辑外面,就对数据库或者是 Redis 中的那个分段库存进行操作即可,包含查库存 -> 判断库存是否短缺 -> 扣减库存。
这相当于什么呢?相当于一个 20 毫秒,能够并发解决掉 20 个下单申请,那么 1 秒,也就能够顺次解决掉 20 * 50 = 1000 个对 iphone 的下单申请了。
一旦对某个数据做了分段解决之后,有一个坑大家肯定要留神:就是如果某个下单申请,咔嚓加锁,而后发现这个分段库存里的库存有余了,此时咋办?
这时你得主动开释锁,而后立马换下一个分段库存,再次尝试加锁后尝试解决。这个过程肯定要实现。
分布式锁并发优化计划有没有什么有余?
有余必定是有的,最大的有余,大家发现没有,很不不便啊!实现太简单了。
- 首先,你得对一个数据分段存储,一个库存字段原本好好的,当初要分为 20 个分段库存字段;
- 其次,你在每次解决库存的时候,还得本人写随机算法,随机筛选一个分段来解决;
- 最初,如果某个分段中的数据有余了,你还得主动切换到下一个分段数据去解决。
这个过程都是要手动写代码实现的,还是有点工作量,挺麻烦的。
不过咱们的确在一些业务场景里,因为用到了分布式锁,而后又必须要进行锁并发的优化,又进一步用到了分段加锁的技术计划,成果当然是很好的了,一下子并发性能能够增长几十倍。
该优化计划的后续改良
以咱们本文所说的库存超卖场景为例,你要是这么玩,会把本人搞的很苦楚!
再次强调,咱们这里的库存超卖场景,仅仅只是作为演示场景而已,当前有机会,再独自聊聊高并发秒杀零碎架构下的库存超卖的其余解决方案。
举荐浏览
字节跳动总结的设计模式 PDF 火了,完整版凋谢分享
刷 Github 时发现了一本阿里大神的算法笔记!标星 70.5K
如果能听懂这个网约车实战,哪怕接私活你都能够月入 40K
为什么阿里巴巴的程序员成长速度这么快,看完他们的内部资料我懂了
程序员达到 50W 年薪所须要具备的常识体系。
对于【暴力递归算法】你所不晓得的思路
看完三件事❤️
如果你感觉这篇内容对你还蛮有帮忙,我想邀请你帮我三个小忙:
点赞,转发,有你们的『点赞和评论』,才是我发明的能源。
关注公众号『Java 斗帝』,不定期分享原创常识。
同时能够期待后续文章 ing????