设计一个秒杀系统
抢红包,购物,买票,“秒杀”场景处处可见。简单讲:秒杀就是在同一个时刻有大量的请求争抢购买同一个商品并完成交易的过程。用技术的行话来说就是大量的并发读和并发写。
秒杀系统本质上就是一个满足大并发、高性能和高可用的分布式系统。
如何在满足一个良好的架构的分布式系统基础上,针对秒杀这种业务做到极致的性能改进。
从整体思维讲,秒杀其实主要解决两个问题,并发读,并发写。并发读的核心优化理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据,并发写原则一样,要求我们在数据库层面独立出来一个库,做特殊处理。另外,还要针对秒杀系统做一些保护,针对意料之外的情况设计兜底方案,以防止最坏的情况发生。
而从一个架构师的角度来看,要想打造并维护一个超大流量并发读写,高性能,高可用的系统,在整个用户的请求路径上浏览器到服务器我们要遵循几个原则,保证用户请求数据尽量少,请求数尽量少,路径尽量短,依赖尽量啥,并且不要单点。。。
秒杀架构可以概括为 稳 准 快 几个关键字。
稳:满足高可用,流量符合预期要稳,超出预期也能扛得住,保证秒杀活动顺序完成
准:例如,秒杀10台手机,就能值成交10台,多一少一都不行。
快:说的是系统稳定性要足够高,足以支撑大流量,做到服务器性能优化,请求链路协同优化。
而对应架构上的就是高性能,高可用,一致性。
高性能
从设计数据动静分离,热点与发现隔离,请求的削峰与分层过滤,服务端的优化4个方面考虑
一致性
有限数量的商品,在同一时刻呗很多倍的请求同时减库存,减库存又分为“拍下减库存”和“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性。
高可用
无论设计思路多么极致,也会遇到想不到的问题,所以任何状态下,需要考虑planB来兜底。
1、设计秒杀系统应该注意的5个架构原则
1.1架构原则:4要1不要
数据要尽量少:
数据在网络上传输需要时间,不管请求数据还是返回数据都需要服务器处理(压缩和字符编码)这些损耗CPU,简化页面大小,去掉不必要的页面装修效果,可以减少CPU的使用
请求数要尽量少:
浏览器渲染页面还要包含额外请求(如CSS/JS/图片等),例如建立连接需要三次握手,页面依赖连接数限制,或者域名DNS解析,会耗时,所以减少请求次数,可以显著降低资源消耗。可以采用:合并SCC/JS文件,在URL中用逗号隔开(https://g.xxx.com/tm/xx-b/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpljs,module-ocus/index.xtpl.js
路径要尽量短:
所谓路径就是用户发出请求返回数据这个过程中,需要经过的中间的节点数。
就是就是一个Socket连接。每增加一个节点就会增加不确定性,所有减少节点路径,可提升性能
依赖要尽量少:
所谓依赖,指的是要完成一次用户请求必须依赖系统或者服务,这里是强依赖,
比如:要展示秒杀页面,而这个页面,必须强依赖商品信息,用户信息
还有其他优惠券,成交列表等这些对秒杀不是非要不可(弱依赖),在紧急情况可以去掉。
要减少依赖可以将系统进行分级,比如0级系统、1级系统、2级系统,0级系统如果是最重要的,那么0级系统强依赖也是最重要的,以此类推。
不要有单点
系统中的单点可以说是系统架构上的大忌,因为单点意味着没有备份,风险不可控,我们设计分布式系统最重要的原则就是“消除单点”。
如何避免单点?关键点是避免将服务的状态和机器绑定,即把服务无状态化
把服务的状态和机器解耦,这些参数可以通过配置中心来动态推送,在服务启动时动态拉取,在配置中心设置一些规则来方便改变这些映射关系。
存储服务本身很难无状态化,因为数据要存储在磁盘上,本身就是机器绑定,这种场景要冗余多个备份方式来解决单点问题。
第一次秒杀系统架构改造来提升系统性能。
这些架构改造包括: 1. 把秒杀系统独立出来单独打造一个系统,这样可以有针对性地做优化,例如这个独 立出来的系统就减少了店铺装修的功能,减少了页面的复杂度;
2. 在系统部署上也独立做一个机器集群,这样秒杀的大流量就不会影响到正常的商品 购买集群的机器负载;
3. 将热点数据(如库存数据)单独放到一个缓存系统中,以提高“读性能”;
4. 增加秒杀答题,防止有秒杀器抢单。 此时的系统架构变成了下图这个样子。最重要的就是,秒杀详情成为了一个独立的新系 统,另外核心的一些数据放到了缓存(Cache)中,其他的关联系统也都以独立集群的 方式进行部署
为了进一步提升秒杀系统的性 能,我们又对架构做进一步升级
比如: 5. 对页面进行彻底的动静分离,使得用户秒杀时不需要刷新整个页面,而只需要点击 抢宝按钮,借此把页面刷新的数据降到最少;
6. 在服务端对秒杀商品进行本地缓存,不需要再调用依赖系统的后台服务获取数据, 甚至不需要去公共的缓存集群中查询数据,这样不仅可以减少系统调用,而且能够 避免压垮公共缓存集群。
7. 增加系统限流保护,防止最坏情况发生。 经过这些优化,系统架构变成了下图中的样子。在这里,我们对页面进行了进一步的静 态化,秒杀过程中不需要刷新整个页面,而只需要向服务端请求很少的动态数据。而且, 最关键的详情和交易系统都增加了本地缓存,来提前缓存秒杀商品的信息,热点数据库 也做了独立部署,等等
2、如何才能做好动静分离
秒杀场景中对于系统中要求为: 稳 准 快
怎样快:
一点提高单次请求的数据,分为动态数据和静态数据
一点减少没必要的请求
2.1何为动静数据
所谓“动静分离”,其实就是把用户请求的数据划分为“动态数据”和“静态数据”。
简单说:动静数据主要区别看页面 中输出的数据是否和URL、浏览者、时间、地域相关,以及是否哦含有Cookie等私密数据。
比如:媒体网站的一遍文章,不管谁访问,他都是一样的,这就是一个典型的静态数据,但是他是一个动态网页。
我们访问淘宝,每个人看到的页面可能不一样,淘宝首页包含了很多根据访问者的特征推送的信息,而这些个性化数据就可以理解为动态数据。
2.2如何做动静分离改造
1.URL唯一化,商品详情系统天然的就可以做到URL唯一化,每个商品都有ID来标识
2.分离浏览者相关因素,因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,童工动态请求来获取。
3.分离时间因素,服务段输出的时间也通过动态请求获取。
4.异步化地域因素,详情页上鱼地域相关的因素做成异步方式获取,当然你也可以通过动态请求方式获取,只是这里通过异步更合适。
5.去掉Cookie,服务端输出的页面包含cookie可以通过代码软件来删除,注意这里说的去掉cookie并不是用户端收到的页面就不含cookie了,而是说,在缓存的静态数据中不含有cookie。
6.ESI方案(或者SSI),即在web代理服务器上做动态内容请求,并将请求插入到静态页面,当用户拿到页面是已经是一个完整的页面了,这种方式对服务性能有些影响,但是用户体验较好。
7.CSI方案,即单独发起一个异步JS请求,以向服务端获取动态内容,这种方式服务性能更佳,但是用户端页面可能会延时,体验稍差。
2.3动静分离有几种架构方案
改造把静态数据和动态数据分离后,需要数据重组完整的输出给用户
1、实体机单机部署
方案 1:实体机单机部署 这种方案是将虚拟机改为实体机,以增大 Cache 的容量,并且采用了一致性 Hash 分 组的方式来提升命中率。这里将 Cache 分成若干组,是希望能达到命中率和访问热点 的平衡。Hash 分组越少,缓存的命中率肯定就会越高,但短板是也会使单个商品集中 在一个分组中,容易导致 Cache 被击穿,所以我们应该适当增加多个相同的分组,来 平衡访问热点和命中率的问题。 这里我给出了实体机单机部署方案的结构图,如下
Nginx+Cache+Java 结构实体机单机部署
实体机单机部署有以下几个优点: 4. 没有网络瓶颈,而且能使用大内存; 5. 既能提升命中率,又能减少 Gzip 压缩; 6. 减少 Cache 失效压力,因为采用定时失效方式,例如只缓存 3 秒钟,过期即自动失效。
2、统一Cache层
所谓统一 Cache 层,就是将单机的 Cache 统一分离出来,形成一个单独的 Cache 集群。 统一 Cache 层是个更理想的可推广方案,该方案的结构图如下
将 Cache 层单独拿出来统一管理可以减少运维成本,同时也方便接入其他静态化系统。 此外,它还有一些优点。
7. 单独一个 Cache 层,可以减少多个应用接入时使用 Cache 的成本。这样接入的应 用只要维护自己的 Java 系统就好,不需要单独维护 Cache,而只关心如何使用即 可。
8. 统一 Cache 的方案更易于维护,如后面加强监控、配置的自动化,只需要一套解 决方案就行,统一起来维护升级也比较方便。
9. 可以共享内存,最大化利用内存,不同系统之间的内存可以动态切换,从而能够有 效应对各种攻击。 这种方案虽然维护上更方便了,但是也带来了其他一些问题,比如缓存更加集中,导致:
10. Cache 层内部交换网络成为瓶颈;
11. 缓存服务器的网卡也会是瓶颈;
12. 机器少风险较大,挂掉一台就会影响很大一部分缓存数据。 要解决上面这些问题,可以再对 Cache 做 Hash 分组,即一组 Cache 缓存的内容相同, 这样能够避免热点数据过度集中导致新的瓶颈产生
3、上CDN
在将整个系统做动静分离后,我们自然会想到更进一步的方案,就是将 Cache 进一步 前移到 CDN 上,因为 CDN 离用户最近,效果会更好。 但是要想这么做,有以下几个问题需要解决。
13. 失效问题。前面我们也有提到过缓存时效的问题,不知道你有没有理解,我再来解 释一下。谈到静态数据时,我说过一个关键词叫“相对不变”,它的言外之意是“可 能会变化”。比如一篇文章,现在不变,但如果你发现个错别字,是不是就会变化 了?如果你的缓存时效很长,那用户端在很长一段时间内看到的都是错的。所以, 这个方案中也是,我们需要保证 CDN 可以在秒级时间内,让分布在全国各地的 Cache 同时失效,这对 CDN 的失效系统要求很高。
14. 命中率问题。Cache 最重要的一个衡量指标就是“高命中率”,不然 Cache 的存 在就失去了意义。同样,如果将数据全部放到全国的 CDN 上,必然导致 Cache 分 散,而 Cache 分散又会导致访问请求命中同一个 Cache 的可能性降低,那么命中 率就成为一个问题。
15. 发布更新问题。如果一个业务系统每周都有日常业务需要发布,那么发布系统必须 足够简洁高效,而且你还要考虑有问题时快速回滚和排查问题的简便性。 从前面的分析来看,将商品详情系统放到全国的所有 CDN 节点上是不太现实的,因为 存在失效问题、命中率问题以及系统的发布更新问题。那么是否可以选择若干个节点来 尝试实施呢?答案是“可以”,但是这样的节点需要满足几个条件:
16. 靠近访问量比较集中的地区;
17. 离主站相对较远;
18. 节点到主站间的网络比较好,而且稳定;
19. 节点容量比较大,不会占用其他 CDN 太多的资源。 最后,还有一点也很重要,那就是:节点不要太多。 基于上面几个因素,选择 CDN 的二级 Cache 比较合适,因为二级 Cache 数量偏少, 容量也更大,让用户的请求先回源的 CDN 的二级 Cache 中,如果没命中再回源站获取 数据,部署方式如下图所示
使用 CDN 的二级 Cache 作为缓存,可以达到和当前服务端静态化 Cache 类似的命中 率,因为节点数不多,Cache 不是很分散,访问量也比较集中,这样也就解决了命中率 问题,同时能够给用户最好的访问体验,是当前比较理想的一种 CDN 化方案。 除此之外,CDN 化部署方案还有以下几个特点:
20. 把整个页面缓存在用户浏览器中;
21. 如果强制刷新整个页面,也会请求 CDN;
22. 实际有效请求,只是用户对“刷新抢宝”按钮的点击。 这样就把 90%的静态数据缓存在了用户端或者 CDN 上,当真正秒杀时,用户只需要点 击特殊的“刷新抢宝”按钮,而不需要刷新整个页面。这样一来,系统只是向服务端请 求很少的有效数据,而不需要重复请求大量的静态数据。 秒杀的动态数据和普通详情页面的动态数据相比更少,性能也提升了 3 倍以上。所以 “抢宝”这种设计思路,让我们不用刷新页面就能够很好地请求到服务端最新的动态数据。
3、有针对性地处理好系统的“热点数据”
假设你的系统存储有几十亿商品,而每天有千万级的商品被上亿的用户访问,那么肯定有一部被大量用户访问的热卖商品,他们很短时间内被大量用户执行访问、添加购物车、下单等操作,这些操作就被成为:“热点操作”。那么问题来了,这些热点对系统有影响吗?为什么要关注。
3.1为什么要关注热点
首先,热点请求会占用大量服务器资源,虽然这个热点可能只占请求总量的亿分之一,然而却可能抢占90%的服务器资源,如果这个热点请求还是没有价值的无效请求,那么对系统资源来说就是极大的浪费。其次,即使这些热点是有效的,我们也需要识别出来针对性优化,从而降低代价。
3.2什么是热点
热点分为热点操作和热点数据,所谓热点操作,例如大量的刷新页面,大量的添加购物车,双十一零点下单等。对系统来说,这些操作可以抽象为读请求和写请求,这两种热点请求的处理方式大相径庭,读请求的优化空间更大一些,而写请求的瓶颈一般都在存储层,优化思路就是根据CAP理论做平衡。
热点数据就是用户的热点请求对应的数据,例如可以通过卖家报名的方式提前筛选出来,对热门商品进行打标。另外还可以通过大数据分析来发现热门商品,比如分析热点商品交易记录,浏览记录,购物车记录,提前分析。
所谓动态热点数据,就是不能被提前预测到,系统在运行过程中临时产生热点,例如:抖音某个商品,一下子就火了,导致他在短时间内被大量购买,由于热点操作是用户行为,我们不好改变,但能做一些限制和保护。
未完......