互联网大厂高并发抢购系统架构设计

背景

大家好,这篇文章给大家介绍一个非常经典的去大厂面试经常被问的一个问题,就是瞬时 高并发抢购问题,通常来说,大厂开发的系统经常会遇到一些类似电商秒杀抢购、景点门票高并发抢购、特殊商品(比如口罩)高并发抢购、类似12306的高并发抢票类的系统。

所以经常会问这一类高并发抢购类的问题,这个时候,小伙伴们如果不能有理有据的给出一整套高并发场景下系统可能遇到的各种问题,以及你对应的架构设计和解决方案,那基本面试可能就会凉掉。

所以今天就手把手带着大家来分析一下,假设在特殊物品库存紧缺的场景下,1分钟内要抢购10w个口罩这类特殊物品,此时可能有数十万人这个量级瞬时涌入来进行抢购,这个时候系统可能会遇到哪些问题,我们应该如何来设计架构解决这类问题呢?

业务架构设计

首先在分析这一类问题的时候,我们先不要考虑这个瞬时高并发到底有多高,先得把实现购买这类特殊商品的一个基础业务架构图画出来,同时把业务流程分析清楚。

大家看下图,如果你要搞一个商品抢购的系统,肯定得有一个抢购系统。这个抢购系统你得依赖商品系统吧,毕竟抢购过程中需要对商品数据进行读写,你还得依赖库存系统进行库存扣减,同时你还得依赖价格系统来计算当前商品的购买价格,还得依赖营销系统来验证商品购买的优惠。

最后还得依赖鉴权认证、风控拦截类的基础系统来确定本次抢购是否可以执行,所以说,一次抢购涉及到的各种系统其实是很多的,完整的基础高并发抢购系统基础业务架构图。 如下图1所示: image.png

图1:高并发抢购系统业务架构设计

网络拓扑架构设计

另外的话,大家还得对你的抢购请求是如何一步一步到达你的抢购系统的,这个事情流程大家也是要画出来的,一般来说,我们的APP移动端对后端访问都是通过一个域名来发起请求的,这个域名会经过DNS进行解析得到我们的SLB负载均衡系统的ip地址。

然后请求会发送到我们的SLB负载均衡系统上去,接着SLB负载均衡系统会把请求均匀分发给我们后端的API网关系统,然后API网关系统再把流量分发给我们的抢购系统,所以大致如下图2所示: image.png

图2:高并发抢购网络拓扑架构设计

好的,当大家能当着面试官的面,麻溜儿的把上面那套业务架构图和生产部署网络拓扑图大致画出来以后,我们可以跟大家保证,虽然这个时候面试官看起来面无表情,但是心里的真实反映应该是这样的:小兄弟可以啊,一般人听到这个问题就直接懵逼了,这小子居然知道先从业务架构和网络拓扑架构入手进行分析。

但是大家别高兴的太早,距离你圆满的完成这个问题的分析,大致是才刚刚走完了西游记十万八千里中的八千里而已,剩下的十万还要继续走呢!这一路上大家马上要遇到各种妖魔鬼怪了!打起精神,接着一起来往下看。

秒杀业务流量洪峰

往往到这里,我们下一步应该分析的,就是日常流量和抢购流量的区别了,什么意思呢,先来说说日常流量,这个意思就是说,平时没有抢购的时候,就是别人正常来买各种商品,系统的大致流量应该是每秒会有多少请求

这个问题的话,不大好说,因为不同的公司其实是不太一样的,但是我们可以取一个较为中间的值,整个系统日常的话每秒也就1000次请求,这个是比较中肯的一个值,不高也不低,如下图3所示。 image.png

图3:日常并发抢购系统业务流量情况

一般来说但凡你的抢购系统以及他依赖的每个系统部署在2台机器以上,每秒1000次请求这种常规流量,各个系统兄弟们同心协力,一起扛一抗,还是没太大问题的。但是如果说搞这么一个活动,某个特殊商品,限量10w份,大家又特别需要他,然后呢,限定就是每天上午10:00开抢,每次都有几十万人眼睛放出红光盯着手机屏幕准备抢他,志在必得,这个时候,流量会搞成什么样子呢?

注意,重头戏来了,大体上来说,根据一般的抢购经验,往往你的10w件商品会在1分钟内抢光,而且根据二八法则,80%的商品会在20%的时间内被抢光,也就是说8w件商品可能会在10s内被抢购,而且参与抢购这8w件商品的流量达到了80%的人群数量,假设一共有50w人参与抢购,就是有40w人在10s内发起抢购请求,抢光了8w件商品。 这个时候,每秒的请求数量应该是40w / 10s = 4w/s的QPS,大家看下图4: image.png

图4:高并发抢购系统业务流量情况

不知道大家看到上图是何感想?脑子别发蒙啊,面试官听得津津有味,咱们赶紧继续往下讲啊,不然你这时候停下来,你们会大眼瞪小眼的!那这个时候如果对你的抢购系统发起的请求量达到了每秒4w,大家觉得会如何呢?

很简单,系统绝对会被打死,网络带宽打满、cpu使用率达到90%多、数据库负载过高、下游依赖频繁超时,这一切问题都可能会发生,你要问为什么?那就是因为你的系统常规化部署下,就是抗每秒1000的请求的,他们又不是设计来抗你每秒4w请求的。

架构设计优化

所以这个时候问题就牵扯到了一个点,那就是怎么才能让你的抢购系统可以抗下来每秒4w请求呢?为了解决这个问题,就得趁着面试官打瞌睡的时候,咱兄弟偷偷给你传授一点武林秘籍了,正常情况下,一台4核8G的机器,开200个线程处理请求,如果他要调用别的服务,或者是访问数据库,基本上每秒单台机器也就抗个1000的请求量。

|并发抢购系统性能瓶颈分析

但是,注意,敲黑板划重点了,不是说你的4核8G机器就菜鸡到了只能抗每秒1000个请求,他的关键问题在于,他要调用别的服务,而且他还要访问数据库,就是因为这种通过网络去访问外部系统,才导致了他每秒抗的请求量比较菜鸡一些,大家看下图5: image.png

图5:并发抢购系统性能瓶颈

大家要知道一点,类似redis、rocketmq这种中间件系统,经过深度优化之后,往往单台抗个上万甚至几万QPS都没问题,所谓的深度优化是什么意思?简而言之就一点,你最好就是每次请求过来,完全就基于自己的内存来读写数据,然后就直接返回了。不要随便通过网络去访问外部的系统,这种情况下,往往你的并发量可以提升几个数量级,如下图6所示: image.png

图6:并发抢购系统架构深度优化

| 并发抢购系统架构优化

所以说,一般这种场景下,有三个非常强悍的优化手段,那就是大幅度减少对外部服务的依赖调用吗;写数据尽量直接写缓存,然后异步写db;读数据尽量优先把数据缓存在系统jvm内存里,本地读取返回。

这里可以给大家举一些例子,比如说,对于特殊商品固定价格抢购,那么对价格系统、营销系统的调用是否就可以省略了,毕竟价格固定,也没有优惠这一说;对于风控和鉴权类的通用操作,是否可以前置到API网关层面让他去执行,从我们的业务系统里移除这类通用逻辑?这不就一下子减少了对4个系统的调用了。

再比如说,对库存的扣减,是否可以让库存系统把数据同步到redis里,我们直接同步扣redis里的库存,然后发mq消息异步去库存系统的db里扣库存?还有比如对商品数据的大量查询,是否可以将商品数据缓存到redis里,同时对热门商品数据全部提前加载到抢购系统的jvm内存里本地缓存? 经过优化后的抢购系统大致看起来是下面图7这样子的。 image.png

图7:并发抢购系统架构缓存优化

大家看上图,这个时候经过一通优化之后,我们的抢购系统已经不再直接调用任何服务了,他在读商品数据的时候,优先都是从自己的jvm本地缓存里读取预缓存的数据,几乎就是纯内存操作,然后扣减库存是去写redis的,对于库存系统甚至是订单系统的数据库中的扣减库存和下单,都是通过MQ异步化执行的。

基本上系统优化到这个水准,主要给抢购系统多部署几台机器,就可以抗下每秒几万高并发的请求了。但是这个时候完了吗?当然没有,这个时候系统里存在的问题还非常的多,我们得继续往下分析,进一步一步一步的优化。

1)高并发抢购系统缓存击穿问题分析与解决方案

首先,分析第一个问题,就是商品数据缓存在抢购系统jvm本地 缓存时的击穿问题,我们在抢购系统的jvm本地缓存中放的数据,一般都是要设置一个过期时间的,因为如果你一直缓存在jvm里,会导致商品数据有变化了,你也不知道,所以假设我们设置一个30min的过期时间,每隔30min过期下,过期之后,抢购系统就得去redis里查商品数据缓存,如果没查到,那就得去调用商品系统的接口从数据库里查了,如下图8。 image.png

图8:高并发抢购系统 — 缓存数据过期问题

那么当你的抢购系统里的本地缓存过期了,此时本地缓存没数据了,然后redis里缓存可能此时也没有的时候,就在这个非常要紧的关头,偏偏就进来了大量的请求,此时这大量请求在本地缓存都没找到,去redis里也没找到,然后呢?然后当然就是完犊子了,因为这些请求都会涌入到商品系统里去,让商品系统从数据库里查询,直接把商品系统击穿,如下图9。 image.png

图9:高并发抢购系统 — 缓存击穿问题

所以这个时候,我们往往需要对这种本地缓存做一个特殊的方案设计,那就是对于本地缓存不要采取这种让他自动过期然后请求过来的时候读取不到再去商品系统那里查找的模式,而是采取抢购系统针对本地缓存自动定时刷新。

也就是说,抢购系统内可以开一个后台线程,然后让他每隔30min自动去redis里查最新缓存数据,或者去商品系统查最新缓存数据,然后刷新本地缓存,这样就可以避免说自动过期后突然大量请求查不到缓存都涌入商品系统了,如下图10。 image.png

图10:高并发抢购系统 — 缓存自动刷新机制

如果大家对缓存击穿问题落地解决方案感兴趣的话,可以 关注公众号 石杉的架构笔记 免费获取 儒猿全网订阅20000+的《亿级流量电商详情页系统实战》,从零落地生产级数据库与缓存双写一致性方案代码。

2)高并发抢购系统数据不一致问题分析与解决方案

再来看下一个比较常见的问题,就是扣库存的缓存与db不一致问题,这个问题的场景可能发生在如下情况,就是说你在redis里扣完了库存之后,通过MQ发送了一个消息异步让那个库存系统在db里扣库存,可是人家库存系统还没在db里扣减呢,这个时候你突然因为异常回滚了这次库存扣减,此时redis里把扣的库存恢复了,然后发了一个消息到MQ去恢复库存扣减,如下图11。 image.png

图11:高并发抢购系统 — 数据不一致问题(一)

但是这个时候redis里的库存是恢复了,可是库存系统db那里就是未必了,因为库存系统从MQ里获取消息的时候,很有可能是乱序获取的,就是先获取到恢复库存的消息,此时库存系统一般会判断一下,之前是否对这次抢购有过库存扣减日志,如果没有,他就不会去恢复库存,然后接着再获取到扣减库存的消息,此时他就扣减了库存,可是恢复库存的消息再也没机会处理了,如下图12。 image.png

图12:高并发抢购系统 — 数据不一致问题(二)

那么上面会导致什么呢?会导致redis里扣减了库存,又恢复了库存,可是库存系统的db里先获取了恢复库存指令,结果什么都没干,然后又获取了扣减库存指令,反而把库存给扣了,此时缓存和db里的库存是不一致的。

所以针对这个问题,通常都会实现MQ顺序消息,也就是说,把同一个抢购订单的多个库存操作指令发送到MQ的一个分区里去,让他们实现有序,强制要求库存系统必须按照顺序依次获取后执行,这样就会先执行扣减库存指令,再执行恢复库存指令了,如下图13: image.png

图13:高并发抢购系统 — MQ顺序消息

总结

好了,今天这篇文章到这里为止,就给大家讲了一下大厂里我们经常遇到的高并发抢购类系统的架构设计和优化过程,以及缓存击穿与数据乱序不一致问题的分析和解决方案,希望大家在阅读后能在未来面试遇到这类问题的时候,有理有据的逐步分析逐步展开,让面试官看到大家沉稳如水、细致如丝的应变能力。

END

【石杉的架构笔记】读者交流群(领取技术干货、技术分享、职场交流)

扫码回复:666,即刻加入:

想要更深入学习 Spring Cloud Alibaba 在大型项目中的实战,推荐儒猿课堂重磅出品的《大型企业级分布式订单系统项目》,课程详细介绍参见链接:sourl.cn/aict6k

文首订单引流图片.png

猜你喜欢

转载自juejin.im/post/7076246830941470727