赞
踩
曾经有一家巨头公司和我们公司进行战略合作,经过双方的不懈努力及精诚合作,双方公司决定共同举办一场秒杀活动,我们公司提供优质商品和强有力的吸引价格以及使用场景,对方公司提供巨大的用户流量,再加上我们公司自己的用户流量,粗略估算下来有5000万的用户流量。
其实,当时我们的架构是完全支撑不了千万级流量的瞬时冲击的,但是双方老板已经达成协议就要快速干起来,而且给了一个基本无法完成的时间期限。
由于时间紧急,我们公司技术部召开了紧急会议,最终得出结论就是在原有架构基础上增加秒杀的相关接口,增加两个H5页面作为前端秒杀活动的承接页面,然后等待洪水般流量的到来。
当秒杀活动真正开始时,流量远超过我们的估算,很快就卡住不动了,后台服务器内存、CPU、数据库负载等全满负荷了。期间,正常下单的用户也不能正常访问公司App以及下单。不难猜出,这个结果老板肯定是不满意的,要求必须解决,不能终止本次活动。
最后的解决办法是:使用金钱来砸——立刻增加120多台云服务器来承载当时的秒杀活动。那次活动对于技术部门来讲无疑是一个痛苦的经历,也是一个“不光彩”的经历。于是,后面增加了针对秒杀架构的设计。
1
需求分析
其实,整个秒杀的业务场景并不复杂,可即查看参与秒杀的商品信息,加上购买和支付的动作,如下图所示。
秒杀业务最大的挑战在于3点:
瞬时:持续时间极短,对于热门且具备极强竞争力的商品通常只有一秒。
流量巨大:因为价格低廉,商品性价比高,而且正常买是需要很高的价格,所以才会吸引大量的用户来争抢。
数量有限:因为商品的低价且性价比高,所以只有很有限的商品数量参与秒杀。
同时,在保证高并发流量承接的前提下,为了增强用户的体验和活动规则的公平性,以及防止遭到恶意破坏等,特此增加如下需求:
(1)用户在秒杀页面无需一直刷新“抢购”按钮,待秒杀活动开始时,按钮自动点亮。
(2)在公平以及防止恶意破坏的原则下,在下单之前增加验证码的录入,或者答题的相关环节。
(3)库存不能出现问题,即不多扣也不少扣。
(4)整个秒杀活动过程持续10分钟。
通过秒杀的需求描述可得出,当前秒杀活动主要需要预估三块的性能指标:存储容量、并发量、网络带宽。
由于是秒杀活动,且参与的商品基本都是低价高性价比的,数量是非常有限的。所以,在订单存储上基本不用去过多考虑。
针对5000万用户平均每人访问2次,则并发量为每秒16.7万左右(5000w*2/10*60),在预留一部分,可以预估到每秒25万左右(也可以进行double下)。
在带宽方面,需要进行相关优化,采取数据传输越少越好,假设单条传输在0.5KB,则根据并发量预估网络带宽为:977Mb左右(25w*0.5KB=122MB*8bit=977Mb)。
做任何系统都要考虑非功能性需求,特别是公司的核心系统,当前秒杀业务系统非功能性需求主要体现在如下几点:
高可用,在秒杀活动的整个持续期间内,都能对用户提供服务。
高性能,让每个用户都能感受到极快的秒杀响应,不能出现大批量用户延迟较高的现象。
可扩展,当流量比预期更高时,有平滑扩展的策略(也有部分产品设计成友好的拒绝策略)。
2
概要设计
(1)动静分离:如何保证用户在不刷新页面的情况下,依然能进行秒杀相关数据的获取且不会耽误秒杀活动的开始。
(2)流量分层,针对巨大流量,如何进行有效的防控,以免造成后台服务的不堪重负,以及如何避免前端页面的卡死。
(3)高可用:如何确保后台持续提供服务。
(4)扣减库存:如何有效扣减库存。
动静分离是指,将静态页面与动态页面(或者静态数据与动态数据)解耦分离,用不同系统承载对应流量。这样可以提升整个服务访问性能和可维护性。
商品秒杀页面的静态数据以及动态数据,均是不同的地方提供,如下图所示。
静态数据是指,页面中几乎不怎么变化的数据(即不依据用户的Cookie、基本信息、地域,及时间等各种属性来生成的数据),例如:
CSS和JavaScript中的静态文件。
活动页中的HTML静态文件。
图片等相关资源文件。
其他与用户信息无关的静态数据。
对于这种分离出来的静态数据可以进行缓存。在缓存之后,这些静态数据的访问效率就提高了,系统也更快了。可以使用代理服务器进行静态数据的缓存。
动态数据是指,依据当前用户属性动态生成的数据,在浏览淘宝首页时,每个用户所看到的商品都是不一样的,这就是淘宝的“千人千面”——针对不同用户做不同的推荐;在百度搜索中是依据不同用户的输入条件,以及用户的习惯给出不同的结果页。这其中的数据就是动态数据。
在“秒杀”业务中,商品价格具有强大的吸引力,所以会受到很多用户的关注,但是商品数量是有限的。所以,在千万的用户中可能只有100人能得到商品,对于系统来说,有90%以上的流量属于无效流量。
“秒杀”业务希望有大量的用户来关注“秒杀”活动,但是在用户真正下单时又不能将这些流量全部放过,所以,需要设计一套高效的流量管控方案,来有效地控制请求流量,过滤掉没必要的流量。
对于瞬时流量洪峰可以采用倒三角的分层级逐层控制方式,共分为CDN、反向代理(Nginx)、后端服务及DB这四个层级。接下来,就来看看每一层级是怎么控制流量的,如下图所示。
要想在整个“秒杀”活动持续期间内,依然能对用户提供良好的体验,则秒杀系统架构在设计时不能设计成单节点的架构。
单节点是所有系统设计中的大忌,因为单节点系统意味着系统的不稳定性较高,可能会出现不可用的情况,会给企业带来直接的损失。在系统设计(特别是“秒杀”这类对高并发要求极高的系统)时,必须保证系统的高可用,如下图所示。
对于“秒杀”活动,通常,公司是不允许商品超卖(即下单成功的数量不能大于商品存存数量)的。一旦超卖,则会给公司造成损失。如果被恶意流量利用,则损失是巨大的。
库存对于电商平台来说是一个重要的业务指标,所以在技术上需要合理设计扣减库存,不能出现“超卖”现象。通常,扣减库存常有以下3种方式:
下单扣库存:在用户下单后就扣减库存。
支付扣库存:用户付完款后再扣减库存。
预扣库存:在用户下完订单后,系统会为其锁定库存一段时间,在超过锁定时间后会自动释放锁定的库存。
根据上面讨论,针对当前秒杀架构如下图所示。
如上架构比较简洁,主要分为以下5层。
用户层:用户端的展现部分,主要涉及商品的相关信息及当前“秒杀”活动的信息。
CDN层:缓存“秒杀”活动的静态资源文件。
负载均衡层:拦截请求及分发路由等。
服务层:“秒杀”活动的具体交易的相关逻辑处理。
基础设施层:数据存储、大数据计算及消息推送相关操作。
其部署架构图如下:
3
详细设计
01. 动静分离设计
实施动静分离架构可以采用“分而治之”的办法,即将动态数据和静态数据解耦,分别使用各自的架构系统来承载对应的流量:
对于静态数据,推荐缩短用户请求路径,因为路径越短,访问速度也就越快。另外,即尽可能将静态数据缓存起来。
对于动态数据,一般用户端需要和服务端进行交互才能获取,所以,请求路径较长,访问速度会慢一点。下图展示了动静分离方案。
静态数据访问速度很快,而动态数据访问速度较慢。那么试想下,可以将需要动态获取的数据给提前生成好,然后使用静态页面加速技术来访问吗?如果这样可以,那动态数据访问的速度就变快了。
这样是可以的,需要用到比较流行的“页面静态化”技术。页面静态化技术是指,直接缓存HTTP连接,而不仅是缓存数据。如下图所示,代理服务器根据请求的URL直接将HTTP对应的响应头及响应消息体返回,流程简洁且高效。
流量分层主要体现在对于CDN层、反向代理层、后端服务层以及数据层流量进行控制。
由动静分离技术可以想到:应尽量将尽可能多的数据提前生成,然后将其放入CDN节点缓存中(因为CDN层在物理架构上离用户比较近)。
所以,如果绝大部分的流量都在这一层获取数据,则到达后端的流量会减少很多,如下图所示。
在动静分离方案中,讲到通过“页面静态化技术”加速动态数据的获取,即提前将动态数据生成好,然后对其进行静态化处理。
所以,这里就可以依据页面静态化加速技术,通过后端服务Job的方式定时提前生成前端需要静态的数据;然后,将其发送到内容分发服务上;最后,分发服务会将这些静态化页面数据分发到所有的反向代理服务器上,如下图所示。
在“秒杀”业务中,活动详情页上有一个倒计时的模块,用户可以看到当前“秒杀”活动还剩余多少时间开始。
这种逻辑简单的功能可以直接使用Nginx来实现:利用nginx-lua插件,使用lua脚本获取当前Nginx服务器的时间进行计算倒计时。
另外,商品库存数据也可以通过Nginx直接访问分布式缓存来获取,如下图所示。
“秒杀”业务中的商品价格很低,对于用户有很大的吸引力,所以可能会有人利用“秒杀器”进行不公平竞争,且有可能存在竞争对手恶意刷请求的情况。
如果存在这样的情况,那本次活动就是有风险的,万一被恶意流量独占了库存,则会导致正常用户不能抢购商品,也有可能这种恶意的请求会对后端系统造成严重冲击,甚至造成后端系统瘫痪。
对于这种恶意请求,最好有一套机制能提前感知,并将恶意请求提前封存。可以在Nginx层中控制;也可以在Nginx中配置用户的访问频率(例如每分钟只能访问10次);还可以使用Lua脚本编写一些简单业务逻辑的接口,例如,通过调用接口直接封掉指定IP地址或UserAgent的请求。
对于服务层的流量控制,有以下几点建议:
在程序开发上,代码独立,不要与平台其他项目一起。
在部署时,应用独立部署,分散流量,避免不合适的流量影响主体业务。
使用独立域名,或者按照一定的URL规则在反向代理层进行路由。
做好系统保护和限流,进一步减少不必要的流量。
当“到达系统中的请求数”明显大于“系统能够处理的最大请求数”时,可以直接拒绝这些多余的请求,直接返回“秒杀”活动结束的信息。例如,活动开始时的商品库存是100,目前库存只剩50了,如果“每台服务器待处理的请求数”已经超过“商品总库存数(100)”了,则可以直接终止掉多余的请求。
对于请求到数据中的流量,写入的流量就是真正下单成功的流量,即需要扣减库存的动作。有如下建议:
如果不是临时的活动,则建议使用独立的数据库作为“秒杀”活动的数据库。
将数据库配置成读写分离。
尝试去除行锁。
对于数据库行锁的优化,可以通过将商品进行拆分来实现——增加ID,如下图所示。对于单一的“秒杀”活动这会得到显著效果。
从流量分层控制方案可看出,瞬时流量就像被漏斗过滤了似的,应尽量将数据和请求量一层一层地过滤掉。这种流量分层控制核心思想:在不同的层级中尽可能地过滤掉无效的请求,到达“倒三角”最末端的请求才是有效的请求。
在系统设计时想要做到高可用,避免单节点的一个小妙招:将服务无状态化。如果无法完全无状态化(如存储系统),则可以通过冗余多个备份节点的方案来避免单节点。
由于篇幅原因,高可用此处就不再赘述,大家可以查看《高并发系统实战派》一书里面针对高并发系统的真实设计案例,毫无保留的分享出了企业级高并发系统实战。
由于在“秒杀”场景中商品一般优惠力度很大,对用户很具有吸引力,所以,在这种场景中使用“下单扣库存”方式更为合适。
在“秒杀”场景中,大部分用户抱着“抢到就是赚到”的想法,基本都会去付款的,但如果真有竞争对手恶意下单不付款,那我们该怎么办?前面在流量管控中已经说到,可以对请求日志进行实时分析,让风控系统选择出恶意用户,然后将其封停。
在“秒杀”场景中,通过流量分层控制可以分层管控大量的“读”请求。但是,依然会有很大的流量进入真正的下单逻辑。对于这么大的流量,除前面说的数据库隔离外,还需要进一步优化库存,否则数据库读/写依然是系统的瓶颈。
接下来看看如何优化大流量“秒杀”场景中的库存数量扣减操作。
在“秒杀”场景中,如果只是一个扣减库存数量这样的简单流程,则可以先将库存数量直接放在缓存中,然后用分布式缓存(如Redis)的超高性能去应对这种瞬时流量洪峰下的系统挑战。
使用缓存是存在一定风险的,比如,缓存节点出现了异常,那库存数量该怎么算?
使用缓存,不仅要考虑分布式缓存高可用(如何设计可以查看我的新书“高并发系统实战派”),还要考虑各种限流容错机制,以确保分布式缓存对外提供服务。
如果是复杂的扣减库存(如涉及商品信息本身或牵连其他系统),则建议使用数据库进行库存数量的扣减,可以使用异步的方式来应对这种高并发的库存的更新。
①在用户下单时,不立刻生成订单,而是将所有订单依次放入队列。
②下单模块依据自身的处理速度,从队列中依次获取订单进行“下单扣库存”操作。
③在订单生成成功后,用户即可进行支付操作了。
这种方式是针对“秒杀”场景的,依据“先到先得”原则来保证公平公正,所有用户都可以抢购,然后等待订单处理,最后生成订单(如果库存不足,则生成订单失败)。
这样的逻辑,对用户来说体验不是很差。
具体排队逻辑如下图所示。
4
搭建千万级流量“秒杀”系统需要哪些技术
“秒杀”系统的流程并不复杂——只是一个“下单扣库存”的动作,但由于其独特的业务特点,所以在进行系统设计时不能大意。对于瞬时流量洪峰的高并发“秒杀”系统,我们需要什么技术呢?下面来总结一下。
用来应对高并发读的请求,主要涉及以下内容,这些在《高并发系统实战派》一书中详细分享了真实使用场景已经技术方案:
各层级缓存的处理(即多级缓存的技术)
分布式缓存技术
LVS
Nginx
消息队列技术
排队系统技术
系统模块化划分
微服务架构思想
日志监控
服务监控
更多相关内容尽在《高并发系统实战派:集群、Redis缓存、海量存储、Elasticsearch、RocketMQ、微服务、持续集成等》一书!
限时下单立减50,快快扫码抢购吧!
送书规则
本次福利将送出《高并发系统实战派:集群、Redis缓存、海量存储、Elasticsearch、RocketMQ、微服务、持续集成等》* 5本
您只需要点击下方卡片,关注公众号,并发送关键词:20221028 即可参与抽奖。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。