如何设计一个秒杀系统 -- 动静分离方案

何为动静数据:
所谓“动静分离”,其实就是把用户请求的数据(如HTML页面)划分为“动态数据”和“静态数据”。

简单来说,动态数据和静态数据的主要区别就是看页面中输出的数据是否和URL,浏览者,时间,地域相关,以及是否含有Cookie等私密数据。比如:

1、某一博客的文章内容不管是谁访问都是一样的,所以它就是一个典型的静态数据,但是它是个动态页面。
2、我们访问淘宝的首页,每个人看到的页面都是不一样的,因为淘宝首页包含了很多根据访问者的兴趣推荐的信息,而这些个性化的信息就可以理解为动态数据了。

如何对静态数据做缓存呢?

第一,应该把静态数据缓存到离用户最近的地方,静态数据就是那些相对不会变化的数据,因此可以把他们缓存起来。缓存到哪里呢?常见的有三种,用户浏览器里,CDN上或者在服务端的cache中。根据情况,把它们尽量缓存到离用户最近的地方。

第二,静态化改造就是要直接缓存HTTP连接。相比于普通的数据缓存而言,静态化改造是直接缓存HTTP连接而不是仅仅缓存数据,如图,web代理服务器根据请求URL,直接取出对应的HTTP响应头和响应体然后直接返回,这个响应过程简单得连HTTP协议都不用重新组装,甚至连HTTP请求头也不需要解析。

在这里插入图片描述
第三,让谁来缓存静态数据很重要。不同语言写的cache软件处理缓存数据的效率也各不相同。以Java为例,因为Java系统本身也有其弱点(比如不擅长处理大量连接请求,每个连接消耗的内存较多,servlet容器解析HTTP协议较慢),所以可以不在Java层做缓存,而是直接在web服务器层上做,这样就可以屏蔽Java语言层面的一些弱点。而相比,web服务器(如Nginx,Apache,Varnish)也更擅长处理大并发的静态文件请求。

如何做动静分离的改造?
以典型的商品详情系统为例,从以下5个方面来分离动态内容。

1、URL唯一化:商品详情系统就可以左到URL唯一化,比如每个商品都由ID来标识,那么http://item.xxx.com/item.htm?id=xxxx 就可以作为唯一的 URL 标识。为啥要 URL 唯一呢?前面说了我们是要缓存整个 HTTP 连接,那么以什么作为 Key 呢?就以 URL 作为缓存的 Key,例如以 id=xxx 这个格式进行区分。

2、分离浏览者相关的因素:浏览者相关的因素包括是否已登录,以及登录身份等,这些相关因素我们可以单独拆分出来,通过动态请求来获取。

3、分离时间因素:因为系统时间要以服务端的时间为准,所以服务端输出的时间也通过动态请求获取。

4、异步化地域因素:详情页面上与地域相关的因素做成异步方式获取。

5、去掉cookie:服务端输出的页面包含的cookie可以通过代码软件来删除。如 Web 服务器 Varnish 可以通过 unset req.http.cookie 命令去掉 Cookie。注意,这里说的去掉 Cookie 并不是用户端收到的页面就不含 Cookie 了,而是说,在缓存的静态数据中不含有 Cookie。

分离出动态内容之后,如何组织这些内容就变得非常关键了。因为其中很多动态内容都会被页面中的其他模块用到,如判断该用户是否已登录,用户ID是否匹配等,所以这个时候应该把这些信息JSON化,以便前端获取。

动态内容的处理通常有两种方案:
1、ESI(Edge Side Includes)方案:即在web代理服务器上做动态内容请求,并将请求插入到静态页面中,当用户拿到页面时已经是一个完整的页面了。这种方式对服务端性能有些影响,但是用户体验较好。

2、CSI(Client Side Include)方案:即单独发起一个异步JS请求,以向服务端获取动态内容。这种方式服务端性能更佳,但是用户端页面可能会延时,体验稍差。

动静分离的几种架构方案:
通过改造把静态数据和动态数据做了分离,那么如何在系统架构上进一步对这些动态和静态数据重新组合,再完整地输出给用户呢?

这就涉及对用户请求路径进行合理的架构了。根据架构上的复杂度,有3种方案可选:

方案1:实体机单机部署
这种方案是将虚拟机改为实体机,以增大cache的容量,并且采用了一致性hash分组的方式来提升命中率。这里将Cache分成若干组,是希望能达到命中率和访问热点的平衡。因为Hash分组越少,缓存的命中率肯定就越高,但是缺点是也会使单个商品集中在一个分组中,容易导致Cache被击穿,所以应该适当增加多个相同的分组,来平衡访问热点和命中率的问题。
在这里插入图片描述
Nginx+Cache+Java 结构实体机单机部署有以下几个优点:
1、没有网络瓶颈,而且能使用大内存。
2、既能提升命中率,又能减少Gzip压缩。
3、减少Cache失效压力,因为采用定时失效方式,例如只缓存3秒,过期即自动失效。

这个方案中,虽然把通常只需要虚拟机或者容器运行的 Java 应用换成实体机,优势很明显,它会增加单机的内存容量,但是一定程度上也造成了 CPU 的浪费,因为单个的 Java 进程很难用完整个实体机的 CPU。
一个实体机上部署了 Java 应用又作为 Cache 来使用,这造成了运维上的高复杂度,所以这是一个折中的方案。如果你的公司里,没有更多的系统有类似需求,只有单个系统需要静态化改造,那么这样做也比较合适,如果你们有多个业务系统都有静态化改造的需求,那还是建议把 Cache 层单独抽出来公用比较合理,如下面的方案 2 所示。

方案2:统一Cache层
所谓统一Cache层,就是将单机的Cache统一分离出来,形成一个单独的Cache集群。统一Cache层是个更理想的推广方案,结构图如下:
在这里插入图片描述
在这里插入图片描述
将cache层单独拿出来统一管理可以减少运维成本,同时也方便接入其他静态化系统。此外,还有一些优点:
1、单独一个cache层,可以减少多个应用接入时使用cache的成本。这样接入的应用只要维护自己的Java系统就好,不需要单独维护Cache,而只关心如何使用即可。
2、统一Cache的方案更易于维护,如后面加强监控,配置的自动化,只需要一套解决方案就行,统一起来维护升级也方便。
3、可以共享内存,最大化利用内存,不同系统之间的内存可以动态切换,从而能够有效应对各种攻击。

这种方案虽然维护上更加方便了,但是也带来了一些其他问题,比如缓存更集中,导致:
1、Cache层内部交换网络成为瓶颈。
2、缓存服务器的网卡也会是瓶颈。
3、机器少风险较大,挂掉一台就会影响很大一部分缓存数据。

要解决以上这些问题,可以再对Cache做hash分组,即一组Cache缓存的内容相同,这样能够避免热点数据过渡集中导致新的瓶颈产生。

方案3:上CDN
更进一步的方案就是将Cache前移到CDN上,因为CDN离用户最近,效果会更好。

但需解决以下几个问题:
1、失效问题。谈到静态数据时,只是“相对不变”,言外之意就是“可能会变”。比如一篇文章,现在不变,但如果发现错别字,就需要变化了。如果你的缓存时效很长,那将导致用户端在很长的一段时间内看到的都是错的。所以我们需要保证CDN可以在秒级时间内,让分布在全国各地的Cache同时失效,这对CDN的失效系统要求很高。

2、命中率问题。Cache最重要的一个衡量指标就是“高命中率”,不然Cache的存在就失去意义了。同样,如果将数据全部放到全国的CDN上,必然导致Cache分散,而Cache分散又会导致访问请求命中同一个Cache的可能性降低,那么命中率就成为一个问题。

3、发布更新问题。如果一个业务系统每周都有日常业务需要发布,那么发布系统必须足够简洁高效,而且你还要考虑有问题时快速回滚和排查问题的简便性。

从前面的分析来看,将商品性情系统放到全国的所有CDN节点上是不太现实的,因为存在失效问题,命中率问题以及系统的发布更新问题。那么是否可以选择若干个节点来尝试实施呢?可以,但这样的节点需要满足几个条件:
1、靠近访问量比较集中的地区。
2、离主站相对较远。
3、节点到主站间的网络比较好,而且稳定。
4、节点容量比较大,不会占用其他CDN太多的资源。

最后,很重要的一点就是:节点不要太多。

基于上面几个因素,选择CDN的二级Cache比较合适,因为二级Cache数量偏少,容量也更大,让用户的请求先回源的CDN的二级Cache中,如果没命中再回源站获取数据,部署方式如下:
在这里插入图片描述
使用CDN的二级Cache作为缓存,可以达到和当前服务端静态化Cache类似的命中率,因为节点数不多,Cache不是很分散,访问量也比较集中,这样也就解决了命中率的问题,同时能够给用户最好的访问体验,是当前比较理想的一种CDN化方案。

此外,CDN化部署方案还有以下几个特点:
1、把整个页面缓存在用户浏览器中。
2、如果强制刷新整个页面,也会请求CDN。
3、实际有效请求,只是用户对“刷新抢宝”按钮的点击。

这样就能把90%的静态数据缓存在了用户端或CDN上,当真正秒杀时,用户只需要点击特殊“刷新抢宝”按钮,而不需要刷新整个页面。这样一来,系统只是向服务端请求很少的有效数据,而不需要重复请求大量的静态数据。

秒杀的动态数据和普通详情页面的动态数据相比更少,性能也提升了3倍以上。

疑问解答:
1、数据存储在浏览器或CDN上的区别是什么?
在CDN上,我们可以做主动失效,而在用户的浏览器里就更加不可控,如果用户不主动刷新的话,很难主动地把消息推送给用户的浏览器。

2、在什么地方把静态数据和动态数据合并并渲染一个完整的页面关键。
在用户浏览器里合并,那么服务端可以减少渲染整个页面的CPU消耗。如果在服务端合并的话,就要考虑缓存的数据是否进行了Gzip压缩了:如果缓存Gzip压缩后的静态数据可以减少缓存的数据量,但是进行页面合并渲染时就要先解压,然后再压缩完整的页面数据输出给用户;如果缓存未压缩的静态数据,这样就不用解压,但是会增加缓存容量。
秒杀推荐在客户端做,普通商品推荐在服务端做。

3、统一cache层是?缓存单点怎么解决的?同一个商品的缓存再怎么hash都是落到同一个tair节点的
统一cache层的缓存是web型的缓存,如varnish
缓存单点是通过hash分组,即多个分组缓存一样的内容
Hash分组是通过nginx完成的,一个分组的机器配在nginx的stream里

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值