SecnodKill
- 秒杀架构:在非常短的时间内,将一定商品进行购买的行为;
一、架构特点
- 商品有限;秒杀时商品数量一般很有限。
- 瞬时性;几乎所有的用户会在一个预定的时间开始,并且会在一个很短的时间窗口内进行秒杀活动
- 流程简单:一般都是下订单、扣库存、订单支付
二、架构难点
- 高并发:大量用户几乎同时请求,大量请求对后端的服务,DB都有很大的考验,可能会导致后端应用负载高;
- 库存超卖:并发场景下,对于商品库存的处理需要保证
- 带宽:访问秒杀页面的用户剧增,对资源的请求量也是剧增,导致网络带宽也上去了,超过平时网站使用的带宽;
- 直接下单:秒杀的规则是到了秒杀时间才能开始对商品下单购买,在此时间点之前,不能下单。而下单页面也是一个普通的URL,如果得到了这个URL,不用等到秒杀开始就可以下单了,需要避免这个问题,下单之前需要disable下单按钮
- 对现有业务冲击:秒杀活动只是网站营销的一个附加活动,这个活动具有时间短,并发访问量大的特点,如果和网站原有应用部署在一起,必然会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪;
三、架构设计
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/c9ac29abc8e6b664c9b3abdc87572e88.jpeg)
- 整个架构可以分为好多层:客户端(浏览器/App) -> Web站点(Nginx/其他类型的网关) -> 服务层(处理逻辑的应用) ->数据库层(相关数据持久化,扣减库存,创建订单等)
1.浏览器端:最上层,会执行到一些 JS 代码
2.站点层:这一层会访问后端数据,拼HTML页面返回给浏览器
3.服务层:向上游屏蔽底层数据细节,提供数据访问,业务逻辑处理
4.数据层:最终的库存是存在这里的,MySQL是一个典型(当然还有会缓存)
四、优化
4.1 优化角度
4.1.1 限流
- 限制到达DB的请求数量,不要让数据库处理请求这么多请求。尽量将请求拦截在系统上游(不要让锁冲突落到数据库上去)。请求都压到了DB,数据读写锁冲突严重,并发高响应慢,导致几乎所有请求都超时,下单成功的有效流量甚小。
4.1.2 削峰
- 秒杀瞬间有大量用户涌入导致很大的瞬间请求峰值,可以借助一些方案让这种突发式的流量转换为相对平缓式的流量。比如消息队列、延长峰值时间窗口(答题,校验码等)。
4.1.3 异步处理
- 异步处理也是削峰的一种体现,除了MQ处理请求的异步,业务也可以异步,比如下单和支付,操作成功和邮件等。
4.1.4 缓存
- 充分利用缓存,尤其是读多写少的应用场景,秒杀也是如此,真正的下单不多,很多都是读取库存数据等。
- 并发高系统DB往往是整个系统的瓶颈,通过缓存可以减少数据库的磁盘IO。比如下订单、扣减库存流程中,在这些流程中主要用到OLTP的数据库,如MySQL。由于数据库底层采用B+树的储存结构,对于随机写入与读取的效率相对较低。如果把部分业务逻辑迁移到内存的缓存或者Redis中,会极大的提高并发效率
4.1.5 其他
- 业务隔离: 比如对相关数据进行预热、在部署上和其他业务分开部署、单独启动Cache或者DB集群来存储对应的数据
- 静态数据存储到CDN:CDN缓存静态数据,避免大量静态数据请求到后端,
4.2 优化描述
4.2.1 客户端(浏览器层,APP层)
- 防止提前下单:在静态化页面中加入一个JS文件引用,该JS文件包含活动是否开始的标记以及开始时的动态下单页面的URL参数。同时,这个JS 文件是不会被CDN 系统缓存的,会一直请求后端服务的,所以这个JS 文件一定要很小。当活动快开始的时候(比如提前),通过后台接口修改这个JS文件使之生效
- 静态数据缓存:秒杀活动开始前用户就可以访问该页面了,可将静态资源资源,比如 CSS、JS、图片、商品详情分发到CDN节点上,避免走后端或者数据库,减少后端压力。
- 拦截重复提交的请求:比如点击下单之后,短时间不能再重复点击,控制X秒之内之内提交一次请求
这种方式只能拦截普通用户,无法抵挡专业程序员
4.2.2 Web站点层
- 拦截单个用户的频繁访问:如果采用一些工具,那么客户端的拦截手段可能无效,站点层可以通过用户的uid来做拦截,单个用户X秒内只能允许一个请求,而X秒内其余的请求如何处理呢?返回一个页面缓存,也就是说一个用户X秒内到达的请求都会返回同一个页面。如此限流,既能保证用户有良好的用户体验(没有返回 404)又能保证系统的健壮性(利用页面缓存,把请求拦截在站点层了)。
- 页面缓存不一定要保证所有站点返回一致的页面,直接放在每个站点的内存也是可以的(注意站点数量可能不止一个)。优点是简单,坏处是 HTTP 请求落到不同的站点,返回的车票数据可能不一样。
+另外:处理单个用户的请求频率,对于大量请求同时间段查询同一个商品时,也可以做页面级别缓存,不管下回是谁来访问,只要是这个商品就直接返回
该方式是减少单个用户的重复请求,但是这样的方式无法抵挡真正的高并发场景,比如就是有1000W用户,每个用户点击一次,
那还是扎扎实实有1000W的请求过来。
4.2.3 服务层(缓解DB的压力)
- 对于双11这样的全民抢购应用场景,原本用户就很多,做好前两块优化是远不够的。虽然用户很多但是库存是可以提前知道的。因此,开发人员可以通过消息队列或者缓存来限制访问到数据库层实际的数量。而对于超出库存的,则前端可以返回等待中,定期再进行重试,直到库存为0为止。
- 缓存:缓存在这一层主要针对读请求,缓存中间件的QPS通常很高,缓存可以拦截住大部分的读请求,少量mis的读和写请求会分数据库。比如商品余量就可以放在缓存,
- 消息队列异步:服务层可以通过消息队列将写请求放进队列,由写服务从队列消费,并发度交给写服务来控制。
- 业务异步:可以将业务异步,比如下单和支付分离,提高并发。
- 因此需要后端流量控制,可以通过消息队列、异步处理、提高并发等方式解决。对于超过系统水位线的请求,直接采取 「Fail-Fast」原则,拒绝掉
4.2.4 数据库层
- 经过前层的过滤之后,数据库层可以以一个相对平缓的速度去处理,比如并发抢购用户是100W,库存1000,那么消息队列投过来的请求也是一个比1000大不了太多的数字。不过在DB层还是可以通过高可用技术来保证系统的健壮性,比如主从、集群等。
五、小结
1.尽量将请求拦截在系统上游(越上游越好,降低下游的压力,逐渐递减瞬时访问压力,减少最终对数据库的冲击)
2.读多写少的使用缓存(缓存抗读压力);利用缓存与消息队列,提高请求处理速度以及削峰填谷的作用
- 浏览器和APP:做限速。 站点层:按照 uid 做限速,做页面缓存。 服务层:按照业务做写请求队列控制流量,做数据缓存。 数据层:闲庭信步。 以及结合业务做优化
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e1b24305ae9d35722e7bb139488f80f5.png)
六、参考