sails js 在 DJI 官网的应用(三)—— 缓存
发布于 8 年前 作者 felling 5273 次浏览 来自 分享

“在计算机科学领域,有两大难题。缓存失效和命名” – Phil Karlton

1、为什么要缓存?

为了提升node程序的性能,我们通常会使用缓存,来加快响应速度。官网项目,不包含数据库,所有的数据,全部通过后台接口调用,要是不使用缓存,页面的响应速度估计会慢到哭。

2、使用什么缓存?

缓存方案,从前到后基本有这么几种,LRU应用内缓存,memcache,redis应用外缓存,静态化缓存,客户端缓存。

   a. 应用内缓存,效率理论上来说是最高的,直接从应用程序的自己的内存中读取。LRU只是一种实现方案,但是,为了保证垃圾回收的效率,node应用可以使用的V8堆内存是有限的,除了存不了多少数据外,存储内存的增加,会导致运算内存的减少,影响程序的性能。同时,数据与逻辑的耦合,也使得,数据在多进程模式下很难分享并同步状态。另外,进程一旦终止,也意味着,数据同时消失。在大中型规模的应用场景中,几乎不实用。

   b,  应用外缓存,即不使用应用自身的内存,将数据存储于系统内存中,供程序调用的方式,优点很明显,不管是多进程,还是多服务器的场景都可以轻松扩展,实现数据共享,确保数据状态的一致性(注意读写分离)。memcache,redis在这里都是比较强大的,但是redis更加优秀,支持多种数据类型,拥有发布、订阅机制,可以定时备份数据到硬盘,及时停机后,也可以恢复数据。目前,aws,ali都有提供相关的redis云服务(也可自建服务)。依托于此,我们基本可以保证数据安全与服务稳定。

    c. 静态化缓存,node的服务架构中,基本会有nginx反向代理这个环节,nginx是十分优秀的静态服务器,我们将页面静态化为html文件,使得nginx可以直接响应这些文件,而不经过应用逻辑的处理,可以使得应用的响应速度,抗压,抗并发能力,稳定性大大提升。

    d. 客户端缓存,是一种前端缓存技术,通过设置响应头,在浏览器支持的条件下,浏览器会缓存页面到指定时间。这种技术,通常用于前端性能优化,于服务器而言,虽然减少了一些请求,但是对其它客户端请求,缓存效果半点没有。过期策略也得考虑清楚先。

我们用的什么方案?这个有一个演进的过程: 开始是三种方案的叠加,及 a + b + c. 应用内缓存保证速度,应用外缓存保证进程状态一致,静态缓存保证稳定性。但是,后来发现这个缓存结果太复杂了,给缓存管理带来了极大麻烦,应用内缓存的与应用外缓存,从性能提升的角度来看,理论上有一定优势,但是,区别也不大,对于要付出的管理成本而言,意义不大,所以去除了应用内缓存。

现在是 b + c,这种结构用了相当长一段时间,效果也还可以。但是,后来为了应对新品发布,将服务器移入了 vpc机房,由于服务器增加到十几台,国外,国内都有服务器,同时服务器没有公网IP,给缓存管理带来了巨大挑战,经常出现一个问题,各服务器之间的数据状态不一致,结果,就是这个机器更新了,那个机器没更新。痛定思痛,我们进一个改进方案,以解决同步问题。

如何保证同步呢?把原本存在各个服务器的数据集中起来,统一放到一个地方,中央集权。以前nginx读取静态文件,需要将这些文件放在本地目录,这就限制了我们的数据集中计划,只能放弃使用静态缓存,进而,完全使用redis缓存,而且不是本地,而是 aws的redis云服务,先由nginx来读取缓存,没有再由node生成。不同的服务器之间由于在同一个局域网内,确保了缓存数据读写的速度。国内国外的这个服务肯定统一不起来,访问速度无法保证。在统一数据的同时,也做了相当多的缓存管理改进工作,这就是接下来的问题

3、如何管理缓存?

“缓存粒度越小,控制难度越大”

缓存管理,有主动(推),被动(拉)两种方式, 1)、主动 当数据发生变更,且改变页面展示时,通知更新。通常情况下,数据的变更主要发生在CMS中,Insert,Update,Delete等操作,都会引起数据变更,进而影响前端展示,但是影响的面积有多大?各有不同的,分为,当前更新(Single),相关更新(Relate)。它的基本机制是,在CMS层面操作,在数据库层面监听,然后,通知缓存进行上述更新。

每个页面对应的缓存结构,如果,拆细了看会比较复杂(产品,类别,新闻,多语言),数据之间的关联性,组合性,会引发相关(横向)更新。同时,如果采用的是页面级别的缓存,那么,所有静态资源(css,js,fonts,img)以及文档(html)结构的变更都被看作数据更新,需要通知缓存更新。 主动(推)更新,对系统设计的整体性,一致性要求较高,变更管理成本也很高。除了时机,也要充分考虑缓存结构,粒度的设计,才能控制缓存变更面积,降低变更成本。

2)、被动 不关心数据变更,只在必要时更新缓存。更新时,从远程拉取(pull)最新数据,重新缓存起来。这种方式的,优点时避开了复杂的主动通知机制,按需更新。缺点是,更新操作要复杂一些,同时可能出现缓存更新不及时,不完全的情况。

我们的缓存管理,以页面为最高粒度,方法为中粒度,数据项为最小粒度,采用被动更新方式。原因如下: 1,我们的数据来自于各个服务端,多套CMS系统,这些服务端都没有一套成体系的数据变更通知机制。 2,数据之间的关联性,粒度,并不明晰,也会增加缓存结构设计复杂度。 3,应用的缓存结构不依赖于主动通知系统。 4,页面缓存,可以直接在nginx层面响应页面数据,提升并发响应能力。

几种key的构成( key --> value ): 页面粒度 , key (page path) —> page html 方法粒度, key (calss_method_param.uniq) —> module data 数据项粒度,key (translate key) —> translate

通常情况下,缓存更新是页面级别的,一旦通知更新,req.cache_update 会被 变为true,应用逻辑会根据这个变量(以及应用配置),来选择是从缓存中拿数据,还是从远程重新拉取,页级缓存也意味着,一旦,页面发生数据变更,与本页面相关的所有缓存都将重新获取,简化控制难度。第一次访问程序,会到node层,并生成缓存,供nginx下次调用。

那么如何通知更新呢?基本规则是带特定的请求参数,如 url ?cache_update=true。可以通过添加快捷键,或者,可视化操作(ams,cms),来简化这个过程,当然,为了防止随意,意外更新以及攻击,需要加入参数及cookie校验等机制。拥有更新权限的人员,包括运营,程序员,产品等数据,应用管理人员。本地开发,测试,预发环境都不建议开启页面缓存设置,便于发现问题。

共享内容如何更新? 例如,页头,页尾等,几乎每个页面必然涉及,一旦发生更新,难道,要把所有页面重新缓存一次吗?这不但效率不高,而且,容易造成程序不稳定。如果,能不能单独的更新页头呢,可以,在nginx层面有一种方案,叫做ssi(server side include)

SSI, 即服务器端包含, include标记在许多模板中都有出现,它用于引入页面包含的公共部分,<inclde file=“header.html”> ,nginx可以解析这种标记,通过内部访问,将文档包含的部分引入,并最终组合为完整文档进行响应。

那么,就可以将页面拆为几个部分,分别存储,这样更新公用部分就方便多了,当然了,没想的那么简单,需要做大量的相关工作,缓存设计,文档拆解,路径替换,node层面的文档组合等。但是,这些工作都是值得做的,只为更丝滑。

3)、一个问题:由于第一次访问时,后端并没缓存,所以,如果存在高并发的情况,第一次,会有大量请求直接到node上,node也可以应对,但没达到缓存的效果。可以采取先生成一次缓存,再开放的方式来解决。在官网目前的情况下,这种场景,只有一种,新品发布时。但是,由于我们采取了A/B发布模式(后面会介绍),缓存已经提前生成了,故不会出现这种问题。

next

系列文章: sails js 在 DJI 官网的应用(一)—— 前言 & 概述 sails js 在 DJI 官网的应用(二)—— 多语言 sails js 在 DJI 官网的应用(三)—— 缓存

下一章节将讲述: sails js 在 DJI 官网的应用(四)—— 部署


DJI官网 团队持续招人中 ,欢迎NB的前端/全栈工程师们,加入我们一起奋斗,打造一流的技术团队, 来吧朋友,国内绝对一流的工资、福利,甚至期权,等你来拿,简历可发送至 fei.pan@dji.com

独聚慧眼  智所未见

1 回复

《Web 开发后端缓存思路》https://cnodejs.org/topic/55210d88c4f5240812f55408

这类干货欢迎多多分享哈

回到顶部