本文授权转自石杉的架构笔记

背景引入

首先,我们一起来看看这个问题的背景?

前段时间有个朋友在外面面试,然后有一天找我聊说:有一个国内不错的电商公司,面试官给他出了一个场景题:

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

他说他当时没答上来,因为没做过没什么思路。其实我当时听到这个面试题心里也觉得有点意思,因为如果是我来面试候选人的话,应该会给的范围更大一些。

比如让面试的同学聊一聊电商高并发秒杀场景下的库存超卖解决方案,各种方案的优缺点以及实践,进而聊到分布式锁这个话题。

因为库存超卖问题是有很多种技术解决方案的,比如悲观锁,分布式锁,乐观锁,队列串行化,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个分段库存字段;

  • 其次,你在每次处理库存的时候,还得自己写随机算法,随机挑选一个分段来处理;

  • 最后,如果某个分段中的数据不足了,你还得自动切换到下一个分段数据去处理。

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

不过我们确实在一些业务场景里,因为用到了分布式锁,然后又必须要进行锁并发的优化,又进一步用到了分段加锁的技术方案,效果当然是很好的了,一下子并发性能可以增长几十倍。

该优化方案的后续改进

以我们本文所说的库存超卖场景为例,你要是这么玩,会把自己搞的很痛苦!

再次强调,我们这里的库存超卖场景,仅仅只是作为演示场景而已,以后有机会,再单独聊聊高并发秒杀系统架构下的库存超卖的其他解决方案。

作者:中华石杉,十余年BAT架构经验倾囊相授。个人微信公众号:石杉的架构笔记(ID:shishan100)

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

每秒上千订单场景下的分布式锁高并发优化实践!相关推荐

  1. Java架构-每秒上千订单场景下的分布式锁高并发优化实践!

    "上一篇文章我们聊了聊Redisson这个开源框架对Redis分布式锁的实现原理,如果有不了解的兄弟可以看一下:<拜托,面试请不要再问我Redis分布式锁实现原理>. 今天就给大 ...

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

    用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景? 转载 codeing_doc 最后发布于2018-11-23 09:44:41 阅读数 1073 ...

  3. 大规模集群下Hadoop NameNode如何承载每秒上千次的高并发访问

    目录 一.问题源起 二.HDFS优雅的解决方案 (1)分段加锁机制 + 内存双缓冲机制 (2)多线程并发吞吐量的百倍优化 (3)缓冲数据批量刷磁盘 + 网络的优化 四.总结 五.参考文章 一.问题源起 ...

  4. 【实践】美团到店综合业务场景下的知识图谱构建与应用实践.pdf(附下载链接)...

    猜你喜欢 0.[免费下载]2021年12月热门报告盘点1.如何搭建一套个性化推荐系统?2.快手推荐系统精排模型实践.pdf3.全民K歌推荐系统算法.架构及后台实现4.微博推荐算法实践与机器学习平台演进 ...

  5. 【实践】短视频场景下信息流广告的挑战和技术实践.pdf(附下载链接)

    今天给大家带来快手磁力引擎舒承椿博士所做的分享<短视频场景下信息流广告的挑战和技术实践.pdf>,关注信息流广告的伙伴们别错过了,另有志于加入快手广告部门的伙伴可以发简历至shucheng ...

  6. 面向削峰填谷的电动汽车多目标优化调度策略 代码主要实现了考虑电动汽车参与削峰填谷的场景下,电动汽车充放电策略的优化,是一个多目标优化

    MATLAB代码:面向削峰填谷的电动汽车多目标优化调度策略 关键词:电动汽车 削峰填谷 多目标 充放电优化 仿真平台:MATLAB YALMIP+CPLEX 主要内容:代码主要实现了考虑电动汽车参与削 ...

  7. redis应用场景—— 缓存,分布式锁,去重

    Redis实际应用场景 https://www.cnblogs.com/mrhgw/p/6278619.html Redis在很多方面与其他数据库解决方案不同: 它使用内存提供主存储支持,而仅使用硬盘 ...

  8. 淘宝商品详情页视频接口(视频参数,sku属性参数,销量参数等页面上的数据均可以采集,支持高并发请求)

    淘宝商品详情页视频接口(视频参数,sku属性参数,销量参数等页面上的数据均可以采集,支持高并发请求)接口代码教程如下: 1.公共参数 名称 类型 必须 描述 key String 是 调用key(必须 ...

  9. 微信10亿日活场景下,后台系统微服务架构实践

    01 微信发展主要的技术里程碑 微信在2011年1月21日发布了1.0版本,以即时消息为主:2011年5月上线了语音对讲.查看附近的人:2012年4年发布了里程碑式的朋友圈功能:2013年游戏中心.表 ...

最新文章

  1. struts2之二(输入校验)
  2. html 点击空白关闭浮层,js中点击空白区域时文本框与隐藏层的显示与影藏问题...
  3. 5分钟教你制作狂拽酷炫的投资交易图
  4. 不要忽视任何小问题!!!一个XML的XPath的问题.....
  5. 洛谷 P1340 兽径管理
  6. 在pandas中遍历DataFrame行
  7. Activiti用户指南之Activiti的API
  8. 移动端日历插件_小程序日历组件开发教程!
  9. Activity之间传递参数
  10. 深度学习李宏毅PPT学习笔记一(深度学习介绍)
  11. GJB用于试验的计算机软件,GJB9001C-2017版标准培训课件(最新修正版).ppt
  12. 2012-7-10可樂词汇积累#9315;
  13. 引用 八卦象数疗法--配方2
  14. Excel —— 相对引用录制宏(附视频)
  15. yamaha php mt8评测,诶哟这个盒子不错哟,NUC 8i5BEK简单开箱+评测(更新完毕)
  16. slides.com 导出PDF
  17. Xilinx ZynqMP相关
  18. 毕业三年,一事无成,被迫回老家,一个决定改变一生。
  19. Java 方式实现词云显示
  20. 投资理财入门18本经典书籍

热门文章

  1. java中字节输入流和输出流的简单使用例子
  2. 图论500题 ---- 并查集求路径上最大值最小不超过K的点对数 HDU Portal
  3. Luogu P6055 [RC-02] GCD(莫比乌斯反演,杜教筛)(这题乐死我了,真就图一乐呗)
  4. 解题报告:【kuangbin带你飞】专题九 连通图
  5. UVA699 下落的树叶 The Falling Leaves(二叉树的递归遍历建树)
  6. gcore java_获取一直FullGC下的java进程HeapDump的小技巧
  7. android系统密码设置功能,手机锁屏密码怎么设置 三种安卓手机锁屏方式推荐
  8. c语言实现天气预报步骤,一份天气预报的制作历程
  9. es任务 如何kill_kill进程的方法
  10. 【idea】Springboot整合jpa