一个技术关键点:数据的动静分离
为什么做数据的动静分离?
对于一个秒杀系统来说,在秒杀的场景下,对系统的要求就是三个方面:一是快,二是准,三是稳定。 如何让系统的更快,主要有两个思路:一是提高每一次请求的效率,二是减少没有必要的请求。 动静分离目的就是让系统更快。
什么是动静分离?
先举个例子:
在没有采用动静分离之前,最早期的秒杀系统是需要刷新整体页面的,后来对系统优化之后,只需要点击一个刷新抢购的按钮就足够了,这种变化本质上就是动静分离的。分离之后,请求的数据量大幅度减少,这样系统响应速度自然就加快了。
什么是动态数据和静态数据?
动静分离就是把用户请求的数据(比如HTMl页面)划分为动态数据和静态数据,他们之间的主要区别就是看看页面中输出的数据是否和URL、用户、时间,用户所在地相关,以及是否含有cookie这些私密的数据。比如:
1、很多媒体类的网站,比如新闻网站,某一篇文章,不管是你访问还是我访问,他都是一样的,这就是典型的静态数据,但是他是一个动态页面。
2、访问淘宝网的首页,每个人看到的页面都会有所不同,首页展示的商品是系统根据每个人的购买记录,点击记录做的个性化推荐,这些个性化的数据就是动态数据。
静态数据不能仅仅理解成传统意义上完全存在磁盘上的HTML页面,它也可能是经过java系统产生的页面,只不过这个页面和URL、用户信息,时间、用户所在地,cookie这些信息无关。
所谓的静态和动态,不是看数据本身是动态静态,二是看数据中是否含有和访问者相关的个性化的数据。
如何对静态数据做缓存?
有几个重点:
1、把静态数据缓存到距离用户最近的地方。缓存的地方一般有是三个:用户浏览器,CDN,还有服务端的Cache当中。
2、静态化改造就是直接缓存HTTP连接。和普通的数据缓存不一样,系统的静态化改造是直接缓存HTTP连接,而不是仅仅缓存数据。比如用户发出URL请求,Web代理服务器根据请求URL,直接取出对应的HTTP响应头和响应体然后直接返回,这个相应过程很简单,不需要组装HTTP协议,也不需要解析HTTP请求头。
3、让谁来缓存静态数据也很重要。不同语言写的Cache软件处理缓存数据的效率不一样。对于java来说,java系统有缺点,比如不擅长处理大量连接请求,每一个连接消耗的内存比较多,Servlet容器解析HTTP协议比较慢,所以可以不再java层做缓存,避免这些缺点,在web服务器上做缓存,比如Nginx,Apache比较擅长处理大并发的静态文件请求。
如何做动静分离的改造?
前面说的是Why和What,接下来说How。把动态页面改造成适合缓存的静态页面,思路就是把他们单独分离出来。
以典型的商品详情系统作为例子。打开淘宝的商品详情页面,看看页面里有哪些动态和静态数据,可以从几个方面分离出动态内容。
1、URL唯一化: 对于商品详情系统,每一个商品都用一个ID来标识,这个ID是唯一的URL。为什么用唯一的URL。因为我们要缓存整个HTTP连接,用什么作为key?就可以用URL作为缓存的key,例如以id=1234这个格式来区分。
2、分离浏览者相关的因素:比如浏览者是否已经登录,浏览者的身份,把这些信息单独拆分出来,通过动态请求来获取。
3、分离时间因素:服务端输出的时间也通过动态请求来获取。
4、把所在地的信息异步化。也可以吧所在地相关的信息通过动态请求来获取。
5、去除Cookie。服务端输出的页面包含的cookie通过代码软件删除。当然并不是用户端接收到的页面不含有cookie,而是在缓存的静态数据中不含有cookie。
如何组织动态数据?
很多动态内容都会被其他模块调用,比如判断用户是否登录,用户ID是否匹配,所以应该用JSON格式组织这些数据,方便前端调用。
动态内容的处理通常有两种方案:ESI Edge side include方案和CSI Client Side include方案。
1、ESI(或者SSI),就是再web代理服务器上做动态内容请求,并且把请求插入到静态页面当中,当用户拿到页面时已经是一个完整的界面。这种方式优点是用户体验比较好,缺点是对服务器性能有负面影响。
2、CSI方案。就是单独发起一个JavaScript请求,向服务端获取动态内容,优点是服务端的性能更好,缺点是用户端的页面可能有延时,体验不太好。
动静分离的几种架构方案:
通过改造把静态数据和动态数据分离开,接下来就要考虑怎么在系统架构上把静态数据和动态数据重新组合,在完整地输出给用户。
根据架构的复杂度,一般有三种方法可以选择:实体机单机部署,统一Cache层,用CDN
1、实体机单机部署
多个虚拟机运行在一个实体机上,会争抢CPU资源,内存资源,网络资源。这种方案就是把虚拟机改成实体机部署,增大cache的容量,并且采用一致性hash分组的方法来提升命中率。如果hash分组比较少,缓存的命中率会提高,缺点是导致同一个商品集中在一个分组,容易导致cache被击穿。所以要适当增加hash分组。
什么是缓存击穿:缓存击穿指的是使用不存在的key进行高并发地查询,缓存中无法命中,导致击穿到后台的数据库系统,大量的数据库查询导致数据库压力非常大。
实体机部署的优点: 1)可以使用大容量内存, 减少cache失效的压力,因为采用了定时失效的方式。例如只缓存3秒钟,过期就自动失效。 缺点是:1)把虚拟机中的java应用换成了实体机,但是单个的java进程很难完整利用机器的CPU,有一定程度的CPU浪费。2)实体机上部署了java应用程序,还作为cache使用,这就造成了运维上的高复杂度。
如果其他系统没有类似的需求,可以这么做。但是如果其他系统也有静态化改造的需求,就不能这么做,而是应该采用把cache层单独抽离出来。也就是方案二
2、统一cache层
这个方法是把单机的cache抽离出来,形成单独的cache集群。优点:
1)单独一个cache层,如果多个应用介入,可以减少成本。因为接入的应用功能只需要维护自己的java系统就行,不需要单独维护cache,只需要关心如何使用就行。
2)统一的cache方案更容易维护,可以减少运维成本,后期的加强监控,配置自动化,只需要一套解决方案就可以。
3)可以共享内存,最大程度利用内存,不同系统之间的内存可以动态的进行切换。
缺点也有
1)cache层内部的交换网络会成为瓶颈。
2)缓存服务器的网卡也会成为瓶颈。
3)机器少风险较大,如果一台挂掉,可能影响很大一部分缓存数据。
解决这些缺点,可以对对cache进一步做hash分组,一组cache缓存的内容相同,防止热门数据过度集中导致新的瓶颈。
3、用CDN
如果把cache进一步往前移动,放到从CDN上,CDN离用户最近,效果更好。不过这么做存在几个问题
1)失效问题,需要保证在几秒之内保证分布在全国各地的cache全部失效,否则长时间部分cache更新,部分cache的内容是错的,这样是不可容忍的。这对CDN失效系统的要求很高。
2)命中率问题。cache最重要的指标就是高命中率,不然cache的存在就失去了意义。
3)发布更新问题,如果一个业务系统每周都有新的业务需要发布,那么发布系统就需要见解高效,还要考虑出现问题,能够快速回滚和快速排查问题。
选择CDN节点时,要考虑几个条件
1)靠近访问量比较集中的区域
2)节点到助战的网络比较好,而且比较稳定。
CDN部署方案有几个特点:
1)把整个页面缓存到用户的浏览器当中。
2)如果强制刷新整个页面,也会请求CDN中的数据。
3)实际的有效请求,用户只是对刷新抢购的按钮进行点击。
这样就把绝大部分的静态数据缓存到了浏览器或者CDN上,真正秒杀时,用户只需要点击抢购秒杀按钮,不需要刷新整个页面。系统请求的知识很少一部分数据,不需要重复请求大量的静态数据,这样可以大幅度提升秒杀系统性能。
参考文献
极客时间的秒杀专栏