当前位置: 首页 > 科技观察

最近火爆的京东抢购飞天茅台是怎么回事?从架构原理的角度分析一波

时间:2023-03-22 15:48:53 科技观察

的背景大家好,本文介绍一个很经典的问题,面试大厂时经常被问到,即瞬时高并发抢购问题。一般来说,各大厂商开发的系统经常会遇到电商闪购、景区门票高并发抢购、特色商品(如口罩)高并发抢购、高并发抢购等系统。ticketrushing类似于12306。所以经常问这种高并发抢购的问题。这时候如果你们不能给出系统在高并发场景下可能遇到的全套问题,以及你们相应的架构设计和解决方案,基本面试可能就凉了。那么今天就手把手的带大家来分析一下。假设在特殊物品紧缺的场景下,1分钟内需要抢购10万个口罩等特殊物品。这个时候可能会有几十万人在这个时候涌入抢购,这个时候系统可能会遇到什么问题,我们应该如何设计架构来解决这样的问题呢?业务架构设计首先,在分析这类问题时,我们不应该考虑瞬时高并发有多高。我们首先要画出一个基本的业务架构图来实现这个特殊产品的购买,同时把业务流程分析清楚。看下面的图片。想要搭建商品抢购系统,就必须要有抢购系统。这个抢购系统,就得依赖商品系统。毕竟抢购过程中需要读写商品数据,还得依赖库存系统。库存抵扣,你要靠价格系统计算出商品的当前进货价,你得靠营销系统来核实商品进货的折扣。最终还是要靠鉴权、风控、拦截等基础系统来决定这次抢购能否执行。所以一个抢购其实涉及的系统很多,一个完整的基本高并发抢购系统图的基本业务架构。如下图1所示:除了网络拓扑的设计,我们还要一步步弄清楚你的抢购请求是如何到达你的抢购系统的。我们还需要绘制这个工作流程。一般来说,我们的APP移动端通过域名向后台发起请求,这个域名会通过DNS解析得到我们SLB负载均衡系统的ip地址。然后请求会发送到我们的SLB负载均衡系统,然后SLB负载均衡系统会把请求平均分配给我们后端的API网关系统,再由API网关系统把流量分配给我们的购买系统。所以大致如下图2所示:OK,当你能把上面的业务架构图和生产部署网络拓扑图大致画在面试官面前时,我们可以向你保证,虽然此时面试官面无表情,但他内心真实的反映应该是这样的:小弟弟可以的。大多数人在听到这个问题时都会立即感到困惑。这小子居然知道先从业务架构和网络拓扑架构入手。分析。但是大家不要太兴奋。从你成功分析完这道题开始,你就刚刚完成了八千里的西游,剩下的十万里你还要继续前行!一路走来,大家很快就会遇到形形色色的妖魔鬼怪!把自己拉起来,然后一起往下看。秒杀业务的流量高峰往往会到达这里。下一步我们应该分析的是日常流量和抢购流量之间的区别。这意味着什么?先说说日常交通吧。这意味着在没有抢购的情况下,其他人正常来购买各种产品。系统的大概流量应该是每秒请求多少。这个问题不好说,因为不同的公司其实是不一样的,但是我们可以取一个比较中间的值。整个系统通常需要每秒1000个请求。这是一个比较中肯的价值。不高也不低。如下图3所示:一般来说,只要你的抢购系统和它所依赖的各个系统都部署在2台以上的机器上,并且每秒请求1000次常规流量,各个系统的兄弟们一起拼起来反对。,仍然不是什么大问题。但要举办这样的活动,某款特产限量10万份,大家特别需要。然后,极限就是每天早上10:00开始抢,每次都有几十万人红着眼睛盯着手机屏幕准备抢他,他志在必得。此时的交通情况如何?请注意,主要事件即将到来。一般来说,根据一般的抢购经验,往往你的10w件商品会在1分钟内售罄,而根据80/20法则,80%的商品会在20%的时间内售罄。也就是说,10s内可能有8万件商品被抢购一空,而这8万件商品的流量已经达到了80%的人群。假设共有50万人参与抢购,即10s内有40万人发起抢购请求。8w件商品已售罄。这时候每秒的请求数应该是40w/10s=4w/sQPS,请看下图4:不知道大家看到上图有什么感受?别上当了,面试官听得津津有味,我们继续聊,不然这时候你就停下来了,你会瞪眼的!这个时候如果向你的抢购系统发起的请求量达到每秒4w,你觉得会发生什么?很简单,系统肯定会卡死,网络带宽爆满,CPU使用率达到90%以上,数据库负载过高,下游依赖频繁超时。所有这些问题都可能发生。你要问为什么?那是因为你的系统常规部署是可以抵抗每秒1000个请求的,他们不是为了抵抗你每秒4w个请求而设计的。架构设计优化那么这次的问题涉及到一个点,就是你的抢购系统如何能够抵抗每秒40000个请求?为了解决这个问题,我们只好趁着面试官打瞌睡的时候偷偷传授给大家一些武功秘籍。一般情况下4核8G的机器开200个线程来处理请求。如果要调用其他服务或者访问数据库,基本上单机每秒可以抗1000个请求。并发抢购系统性能瓶颈分析不过要注意敲黑板划重点。并不是说你4核8G的机器每秒只能抗1000个请求。关键问题是它需要调用其他服务。而且,他还需要访问数据库。正是因为这种通过网络访问外部系统,所以他每秒抗拒的请求数比较少。看下图5:大家应该都知道像Redis、RocketMQ这样的中间件系统,经过深度优化后,往往单台抗几万甚至上万QPS是没有问题的。所谓的深度优化是什么意思?简单的说就是每次请求的时候最好根据自己的内存来读写数据,然后直接返回。不要只是通过网络访问外部系统。在这种情况下,你的并发往往可以提高几个数量级。如下图6所示:并发抢购系统架构优化那么,总的来说,在这种场景下,有3种非常强大的优化方式,那就是大大减少对外部服务调用的依赖;数据尽量直接写入缓存,然后异步写入DB;尽可能读取数据,尽可能将数据缓存在系统JVM内存中,返回本地读取。在这里我可以举一些例子。比如特价商品的固定价格抢购,是否可以省略对价格系统和营销系统的调用?毕竟价格是固定的,没有打折之说。风控、鉴权等通用操作,是否可以前端加载到API网关层面让他去执行,这种通用的逻辑可以从我们的业务系统中去掉?这不会同时减少对4个系统的调用。再比如扣库存,库存系统能不能把数据同步到Redis,我们直接在Redis同步扣库存,然后异步发送MQ消息给库存系统的DB扣库存?比如对商品数据的大量查询,是否可以将商品数据缓存在Redis中,同时可以将所有热门商品数据提前加载到抢购系统的JVM内存中,用于本地缓存?优化后的抢购系统大致如下图7:看上图,此时经过优化,我们的抢购系统不再直接调用任何服务。他在读取商品数据时,先从自己的JVM本地缓存中读取预缓存的数据,几乎是纯内存操作,然后扣除库存写入Redis。对于库存系统甚至订单系统数据库扣库存和下单都是通过MQ异步进行的。基本上,系统优化到这个程度,主要是给抢购系统多部署几台机器,让它能够承受每秒几万的高并发请求。但这一次结束了吗?当然不是,此时系统还存在很多问题,还得一步步继续分析进一步优化。①高并发抢购系统缓存击穿问题的分析与解决方案首先分析第一个问题,即抢购系统JVM本地缓存中商品数据缓存击穿。我们放在抢购系统的JVM本地缓存中的数据,一般是需要设置一个过期时间的,因为如果一直缓存在JVM中,商品数据会在你不知情的情况下发生变化。那么假设我们设置一个30分钟的过期时间,每30分钟过期一次。过期后,抢购系统要检查Redis中的商品数据缓存。如果没有找到,就要调用产品系统的接口去数据库中查询。如图8所示:那么当你的抢购系统中的本地缓存过期时,此时本地缓存中没有数据,此时Redis中的缓存可能不可用,在这个非常关键的时刻,大量进来,这时候大量的请求在本地缓存中没有找到,Redis中也没有,然后呢?那么当然就完了,因为这些请求会涌入商品系统,让商品系统从数据库中查询,直接把商品系统打垮。如图9所示:所以这个时候,我们往往需要针对这种本地缓存做一个特殊的设计,就是对于本地缓存,不要使用这种方式让它自动过期,然后在请求到来的时候完了,不能读了再去商品系统。抢购系统不使用搜索模式,而是定期自动刷新本地缓存。也就是说,你可以在抢购系统里开一个后台线程,让他每隔30分钟自动查看Redis中最新的缓存数据,或者查看商品系统中最新的缓存数据,然后刷新本地缓存,这样就可以避免说自动过期后突然有大量请求在缓存中找不到而涌入商品系统。如下图10所示:②高并发抢购系统中数据不一致的分析及解决方案我们再来看下一个比较常见的问题,就是缓存和DB不一致做库存扣的问题。该问题的场景可能出现在以下几种情况。也就是说,你在Redis中扣完库存后,你通过MQ异步发消息让库存系统扣掉DB中的库存,而对方的库存系统还没有扣完DB中的库存。这个时候你突然因为异常回滚了这一次扣库存,这时候在Redis中恢复扣库存,然后给MQ发消息恢复扣库存。如图11:但是此时Redis中的库存恢复了,但是在库存系统DB中就不一定了,因为库存系统从MQ获取消息的时候,很可能是乱序获取的,即先获取恢复后的库存。消息。这时库存系统一般会判断之前是否有过本次抢购的库存扣减日志。如果没有,他就不会恢复库存,然后得到库存扣减的消息。库存,但恢复库存的消息永远没有机会被处理。下图12:那么上面会导致什么?会导致在Redis中扣除库存,然后恢复库存。但是库存系统的DB先是拿到了库存恢复指令,但是没有做任何事情,然后又拿到了扣减库存指令,但是却扣掉了库存。有时缓存和数据库中的库存不一致。因此,为了解决这个问题,通常会实现MQ顺序消息,即将同一个急单的多条盘点操作指令发送到MQ的一个partition中,使其有序,盘点系统必须按顺序执行获取后,先执行库存扣减指令,再执行库存恢复指令。如图13:总结,今天的文章到此结束,我将给大家讲讲我们在大厂经常遇到的高并发抢购系统的架构设计和优化过程,以及缓存击穿和数据乱序分析和不一致的解决。