一项指标的变好,总少不了相应优化策略的实施。优化并不是简单的一蹴而就,而是个不断迭代与推翻的过程。更深层的优化方案,往往是在某种思维策略之下,对问题场景和基本策略优缺的深刻理解后做出的当下最优的权衡结果。
说到优化,大家在收到“优化指标”任务的时候。通常会做两件事情——分析“优化指标”对应的痛点、寻找解决痛点的技术方案并施行。那这样是否就足够了呢?我的答案是否定的。在我的认知里这只是第一层的优化,虽然在结果上往往我们使用更优的技术后确实可以达到更好的优化效果,但却又不那么完美,优化效果还可以做得更好。那究竟缺了什么呢?下面,我会逐步阐述我的优化思路。首先,普遍的优化思路是基础,我们先来看看在普遍的优化思路下,基本的前端高并发策略是怎么样的?
高并发场景,与普通场景的核心区别是并行的访问量激增。因此,前端高并发策略本质要解决的是由访问量激增带来的问题。那访问量激增带来的是什么问题呢? 我们先来看一张H5正常的访问流图:
而在高并发场景下,若不进行任何的高并发策略应对,原访问流图会变成这样(前端到后台红色部分的请求会被后台拒掉甚至可能会击垮后台):
加强前端“门”的精简过滤能力后,我们期望看到的访问流图是这样的:
前端“门”的角色要加强的是两方面的能力,一个是精简,另一个是过滤。
在精简的技术方案上,实际有两个,一个是最大并发数、一个是最大流量。对应的则是我们并行请求中的请求数和请求大小.
过滤一般有两种形式,一种是被动式的,即只允许特定量的请求通过,超了的部分就进不来了,这策略一般用于后台,叫“过载保护”;另一种则是主动式的,通过对数据时效性的牺牲把数据往更前的一端进行储存。在前端层面,一般叫“本地缓存”,当请求时发现前端有缓存的内容,就不用再去访问服务器了。所以,在过滤的技术方案上,前端可以通过缓存来完成。
完成上述两步——分析本质痛点、寻找可行技术方案,接下来大家普遍的做法是选择其中的合适方案,然后用到我们的项目中。对于合并,我们会把同类型的文件统一做下合并;对于压缩,我们会把没压缩的代码都统一做下压缩;对于缓存,我们统一启用较长的http cache、使用localstorage缓存、使用离线包。整体策略下来,虽然在一定程度上会有效果,但是我认为这往往又是不够的。要做到更彻底的优化,就需要对优化方案和优化场景本身做更深入的思考和策略调整。而这,往往是需要靠相应的思维模式驱动的。下面我来分别说说我总结出来的一些适用于更深层优化的思维,其中会着重谈谈差异化思维。
差异化思维,讲求的是在深入理解技术与场景后,对技术与场景进行差异化分解,以达到每个差异场景的进一步技术最优。
从前两步中——分析本质痛点、寻找可行技术方案,我们了解到高并发应对在前端技术层面可以从合并、压缩、缓存三方面着手。一个很浅显的道理是,这些策略做得越彻底,前端层面能挡掉的并发量就越多。但事实上,往往我们却又并不能这么做,而只能选取其中一种比较折中的方案。
比如,考虑到对页面访问耗时的影响,我们并不会把整个H5项目资源合并成为一个请求。原因在于,从本质上来说,每项纯技术策略,有其优点的同时,就必然会带来或多或少的缺点,正所谓万物有利必有弊。而当这个弊端成为了影响项目核心能力(如体验方面能力中的页面访问耗时)的时候,即使是能更好地提高并发能力的方案,在利弊权衡之后往往最终也并不会采用。这就是我前面说的只选择折中的优化方案时优化不够彻底的原因。 在利弊权衡之下,往往我们会选择一个折中方案(如下图那样中选择的策略3):
而更彻底的优化应该是,了解每个方案所导致的弊端影响,由弊端影响对项目场景进行差异化分析,按各场景对弊端影响面的容忍程度,实行策略方案的差异化。对于能接受弊端影响的场景,使用最优方案;而对于不太能接受弊端影响的场景,使用较优方案;依次类推到使用折中方案。从而做到差异化的精细优化。优化后的总体策略方案会变成类似下图的形式,原有项目只是单纯的使用折中策略3,而差异化处理后,会抽离出部分项目模块使用更优的策略1和策略2。
代码合并,合并到一定程度其弊端就会逐步放大显露。
弊端有:单个请求过大,造成对页面首屏渲染耗时的影响;动静请求合并后(cgi+html),缓存时效性的要求会大大地提高(缓存时效性取决于各合并资源中要求最高者,木桶原理)。
根据每个弊端的影响,下面针对具体场景进行差异化分析。
针对具体场景,差异化地采用相应最优的合并策略,优化效果将会再进一步地提升。
同样的,代码压缩,也有其弊端。弊端有:压缩程度越高,代码可读性越差,不便于线上问题的定位;虽有更优的压缩算法,但算法本身又存在自身的局限性。
与上面的类似,缓存策略同样也有相应的弊端。弊端有:缓存时间越长,数据的准确性就越差,会存在缓存数据虽有效但已与最新数据有较大差异的问题。
“资源时效依赖度”差异化分解 针对资源有效性受缓存时间长短的影响,我们可以对资源进行时效性分级。可大致分成更新可控资源和更新不可控资源。此处的可不可控指的是资源更新后页面能否实时感知到更新。对于前端开发人员部署的js、css、图片等资源,均可作为更新可控资源,设置极长缓存,因为这类资源更新的同时可以将版本信息实时同步给前端页面(如拉取的文件改名了、改时间戳了等)。而对于无法实时同步版本信息给前端页面的资源,可作为更新不可控资源。对于这部分资源,我们可以再根据业务对各资源的时效性要求程度进行差异化分级。
以手Q中的H5项目中用到的QQ头像资源为例,此场景下头像是一个对项目更新不可控的资源。用户通过使用手Q或PC QQ修改自身头像后,各H5项目对于这个修改是无感知的,H5并不会实时地收到更新通知(除非双方在接口层面上做同步通知)。此时,如果头像缓存时间设置较长,就会出现用户更新了头像,但在H5项目中看到的头像还是旧的的情况。但如果不缓存,在高并发场景下势必对头像服务器造成极大的并发压力。这时,就需要对这一更新不可控资源做进一步差异化分解。
对于手Q中社交性较强的H5项目(如手Q红包、手Q AA收款等),其中虽然有很多头像,但是各头像对时效性的要求还是有差异的。最简单的我们可以把头像分成两类,高时效性头像和低时效性头像。稍作分析后,其实可以发现,对于使用者而言,用户自身(主人态)的头像变更是最敏感的,如果用户在手Q或PC QQ上修改完自己的头像后,进入该H5后发现自己的头像没有变是不太能容忍的。此时,用户自身的头像可作为高时效性头像。而对于其他用户(客人态)的头像的时效性,变没变,使用者其实倒不会太过在意,所以对于非用户自身的头像可作为低时效性头像。最后在策略上,对于高时效性头像,缓存一个较短时间;对于低时效性头像,则缓存一个相对较长时间。(实现起来也很容易,可将差异化逻辑放在前端判断然后加时间戳决定缓存时间即可。)
针对具体场景,差异化地采用相应最优的缓存策略,优化效果也将会再进一步地提升。
在差异化思维的指导下,高并发优化策略得到了更进一步的完善。该思维的核心思想是针对方案的优缺与实际场景进行差异权衡。从通用性角度来看,这项思维也适用于工作上的很多事情,是一项通用化的思维,而并不仅仅局限于使用在解决前端高并发这个问题点上。同时,也并非所有的方案都只采用差异化思维就能完美地解决问题。差异化思维只是众多思维中的一种,实际上,还有很多思维。一个优秀的优化方案往往是在多“维”的思考权衡下的最终产物。
比如,边界放大思维,指的是我们在做一项事情的时候,视野不应只停留在自己所能完全把控的领域,而应该把边界放大,从更外围的视野来思考这个问题的解决方案。
如前面说到的缓存策略,其实有一个当前H5的缓存策略弊端需要通过使用边界放大思维来优化。这个弊端是:当前浏览器缓存技术有其自身的局限性,缓存的有效性依赖于用户的二次访问程度。弊端的核心问题在于:缓存时机与用户首次访问相耦合。这使得在一些超高并发的H5活动中(活动类与业务类H5不同,活动类H5大部分是第一次访问的用户),缓存带来的效果并没有想象中大。
这是在纯前端技术层面无法解决的。但当我们把思维边界放大,扩展考虑到承载H5的平台时,这个弊端也许就能获得解决。因为这个弊端核心要解决的是资源的缓存与页面访问解耦的问题,而承载平台方(尤其终端)是有这个能力完成的。如在手Q中,这个解决方案叫“离线包”。离线包支持被动缓存的同时,也支持主动缓存。可将页面内容无须用户主动访问而通过预下载或主动push的方式缓存到用户手Q客户端上。首次访问的用户也可直接命中缓存。这样即可大大提高缓存的有效性。春节期间,手Q各高并发H5无不使用这项技术来提高页面的高并发能力。
再如,逻辑全面性思维,指的是我们在做一项事情的时候,视野不能只停留于逻辑的局部,而应该看到逻辑的全状。如逻辑有正常状态,也有异常状态,我们不能只考虑正常状态。逻辑有双向也有单向,对于双向的逻辑,我们不能只考虑其中的正向。
其实,前面我说的所有前端高并发策略(包括前面画的图),都仅仅只考虑到了数据流的正向逻辑段,即数据从用户端流向服务端的过程。而数据流的反向逻辑段其实是没有考虑的(即数据从服务端回到用户端的这一段逻辑情况)。而在高并发场景下,数据的反向逻辑段往往也会作为逻辑中非常关键的一环。没考虑数据流反向逻辑段的高并发策略,优化数据再好也只能说完成了一半。下面是数据流动的全逻辑过程(红色部分是数据流动的反向逻辑段):
转载这篇文章主要是看中文章作者的主题思想,可以借助四象限分析优化策略,利弊、价值等。技术服务于具体的业务场景,才能更接地气,更体现技术的价值。
附上原文链接:多“维”优化——前端高并发策略的更深层思考