欢迎你们前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~
css
做者: 王斌
兴趣部落项目自2014年至今,一直都是采用的是前端渲染的模式,这种模式就是页面html是一个空壳,首屏的内容须要css和js都加载完成后,请求cgi得到数据后再渲染给用户。这种模式的好处是可让后端和前端的工做彻底分离,给平常的开发和维护带来很大的便利。html
咱们在如今的工做模式上,为了尽量的减小首屏耗时,作了至关多的优化,包括使用离线包的机制来减小css和js的时间。前端
可是这些全部的优化,仍然是基于JS执行后,才能够向用户交付首屏的,若是遇到android上执行JS速度很慢的机器,就会显得耗时仍然特别长。node
使用直出的页面,html再也不只是一个空壳,而是一个渲染良好的页面,这样用户就能够不用等待JS加载和执行后看到内容,大大减小用户的焦虑感。react
在现有的工做模式下,使用同构直出的手段,不只能够保留咱们现有的开发模式,还能够减小不少工做量。试想,咱们如今将现有的工做模式所有推翻使用普通直出,要面临多少工做重建。android
同构直出,先后端彻底使用同一套代码,将前端的渲染逻辑移到服务器端完成,将渲染后的结果再交给用户,得益于React这套体系,咱们将这样的能力应用到了兴趣部落项目中。git
首先咱们先在一个小页面上进行全量尝试,不断解决,调整其间遇到的问题。web
后面,待咱们的架构成熟了以后,咱们把这套体系运用到兴趣部落的三大核心页面之一的帖子详情页。redux
通过不断的灰度、解决问题,最终帖子详情页的同构直出正式全量上线后端
其中的机器,几乎全是v4虚拟机。
直观感觉下对比的效果,以下图
左侧为前端渲染,右侧为同构直出
能够明显看出,直出在android机型下带来的优化效果是很是明显的,同时从正常的测速数据上来看,直出的首屏耗时减小了50%,慢速用户占比减小了3个百分点
前端代码的架构是传统是react+redux架构体系,使用redux的架构可让咱们的直出更可控
内存问题
同构直出大部分状况下都要面临此类问题,普通的前端页面极少会考虑内存泄露的缘由,然而在node端运行的代码都要考虑内存泄露的问题。
一次用户访问的管道中,res.end()调用完了,理论上管道产生的内存能够彻底被回收,若是不能够被回收,那么就会产生内存一直增加的问题。
咱们都知道,挂到GC ROOT上的变量都没法回收,前端的代码太多不控的代码会致使内存泄露,咱们须要一个通用的解决方案
原来的代码
let Main = require(MainEntry); 复制代码
虽然每一个请求,每一个用户都会去require同构的Main组件,可是因为node端require是单例模式,因此每一个用户引用的Main都是同一个引用,每一个请求对Main(Main的执行)内部产生的变量声明,若是该变量链接到Main的引用链上的,当用户请求结束的时候是没法释放的,由于Main的引用是单例的,会node缓存住,因此这些变量就没法回收,会产生严重的内存泄露问题。
上线时内存暴涨的问题
为了解决这个问题,能够对每一个用户请求,开辟一个新的Main实例,这样当用户请求结束了,Main的引用能够被顺利回收,就不会产生内存泄露的问题
目前部落中使用的是vm的解决方案,为每一个用户请求建立了一个沙箱环境
if(! mainVmScriptCache[entry]){ var code = fs.readFileSync(require.resolve(entry), 'utf8'); mainVmScriptCache[entry] = new vm.Script(m.wrap(code), { filename: entry }); } //var startVmTime = + new Date(); var module = { exports: {} }; var exports = module.exports; mainVmScriptCache[entry].runInThisContext()(exports, require, module, __filename, __dirname); Main = exports.default; 复制代码
每一个用户请求过来,都会从新变编译出一个Main, 这个Main引用不与其余请求共享,请求结束了,Main也会被回收,Main中产生的全部垃圾内容都会被一块儿回收
内存获得有效控制
关于性能问题,vm产生的性能会带来CPU的使用耗时增长,大约20ms,但对内存控制是很是有效的。
关于这块的优化,同构直出原本就是一个CPU密集型的任务,后续能够结合缓存来将CPU密集型任务转为内存密集任务
虽然解决这个问题的方案并不难,但重在咱们能在详情页放量前能发现这个经常被忽略的问题。
通用的重构直出方案,到前端的代码会正常执行,这样cgi会在前端再发一次,数据也会变成最新的。可是,实际上,服务器端已经为该用户发一次请求了,这样就致使了一个用户请求了两次cgi。
这里的方案一般能够划为优化的角度去考虑。
在第一个小页面上线的时候,咱们并无过重视这个问题,可是详情页灰度上线的时候,咱们逐渐认识到这不是一个优化问题,而是一个严重的架构问题。若是详情页直接上线,对后台cgi带来量的冲击是很是大的,本来3亿的日访问量一会儿变成6亿的访问量,这比30w变成60w对后台的压力要远远大的多。因此这个问题要在继续放量前必须解决的问题。
解决的方案就是使用数据cache,将node端已请求的数据同时吐到前端去,这样在前端请求的时候作一次拦截,检查是否有数据缓存,若是有的话就再也不请求CGI, 这样能够大大消除新增CGI的量。
可是遇到的问题,数据用url_参数作key存储的时候,每每由于先后端不一致的参数致使缓存没法匹配,好比前端使用了地理位置信息参数,这个在服务器端是没法换取到的。解决的方案就是将这些参数存到cookie里,请求的时候node端能够用cookie缓存的位置信息数据。
(客户端依赖参数使用cookie,缓存命中率大大提升)
css资源、js资源使用离线包是比较想固然的事情,可是在部落转为直出,接入离线包也遇到一些困难。
(js、css md5值不少)
用户端的离线包版本是不少的,每一个离线包版本对就没的资源的md5又不同,直出的页面引用的资源又该怎么知道用户本地离线包的md5是哪一个呢?
咱们使用了以下的解决方案:
在前端编译离线包的时候,会把html内注入一段script,script做用是在当前页面下种下一个表明版本号的数据(version),同时将此html命名成[version].html发送到直出服务器,那入由该离线包发出的直出请求都会带上这个版本信息,咱们根据这个版本信息将对就的[version].html作为本次直出要吐出页面的模板,这样到用户端能够匹配到用户离线包的资源。
首屏优先也是经常被你们忽略的体验问题,大部分前端渲染的页面都是以下的样子
<html> <head> <link href="main.css" /> </head> <body> <div class="root"></div> <script src="lib.js"></script> <script src="render.js"></script> </body> </html> 复制代码
若是使用直出会变成这个样子
<html> <head> <link href="main.css" /> </head> <body> <div class="root"> <h1>title</h1> <div class="content">content</div> </div> <script src="lib.js"></script> <script src="render.js"></script> </body> </html> 复制代码
看起来也没什么问题,内容直接出如今.root里了
可是咱们常常会忽略一个体验问题,这样的页面真的是会比非直出快么?
答案是80%否认的!也不是大部分状况并不会比非直出快!甚至体验上会比非直出更慢!
缘由是要弄清楚浏览器首屏的出现时机,何时浏览器会执行第一次paint ? 简单来说,大部分状况下直出的dom元素并不会第一时间展现出来,而是等render.js执行完,才会展现首屏内容,若是render.js都加载并执行完,那么咱们直出的dom元素还有什么意义,这又回到普通的前端渲染了,空壳架子又比原来还要多了,因此不免白屏时间会更长。
因此为了解决这个问题,咱们要让直出的dom节点能够第一时间展现出来,解决的方法也不难,可使用懒加载,部落使用了更好async方案,第一时间展现首屏内容,第一时间加载JS,而且不阻塞DOM渲染,不阻塞首屏交付。
感谢x5内核同窗weetli的指导
关于首屏渲染时间:
首屏渲染的时机涉及么不少因素,很不可控,可是x5内核浏览器提供给了便利的控制方法来优化首屏时机
x5首屏渲染时机能够本身定义,添加meta标签
<meta name="x5-pagetype" content="optpage"> 复制代码
x5-pagetype有三种可选类型
首屏标签为
<first-screen/>
复制代码
一个线上的后台任务,最大的问题就是讲稳定和容灾,首先任务保证用户的服务是稳定的,遇到一些突发问题时候,线上的页面仍然能够稳定的提供服务。
相比传统的直出,同构拥有更强的容灾的能力,这也同构直出的魅力所在!由于在同构直出宕掉的时候,还有前端渲染页面能够提供正常的服务,因此部落在部署页面的存在两种模式
现有的前端渲染路径:buluo.qq.com/mobile/deta…
对应的直出页面路径: buluo.qq.com/mobile/v2/d…
好比这个直出页面buluo.qq.com/mobile/v2/d… (模拟器打开),去掉v2就是非直出页面buluo.qq.com/mobile/deta… (模拟器打开)
兴趣部落直出项目在容灾策略上提供了两层容灾策略
第一层 框架层 · 超时、出错容错
框架超时、出错时候就会返回一个页面原始的非直出html页面,这样到用户端就能够走正常前端渲染。
第二层 运维层 · 服务宕机容错
这一层的容错会放在服务机的前置层,简单来说就是请求直出页面出现5xx、4xx的错误,就会隐式的转发路径到不含v2的非直出页面。
location ^~ /mobile/v2/ { proxy_pass xxx; proxy_intercept_errors on; error_page 403 404 408 500 501 502 503 504 @buluo_static_page; } location @buluo_static_page { rewrite /v2/(.*)$ /mobile/$1 last; } 复制代码
即便整个直出服务彻底挂掉,咱们都不用担忧服务的可用性
另一个层次,如何保证平时开发过程的稳定性,也是整个架构体系重要的一环,不要等到有问题的代码的发到线上才发现有问题。
部落在直出的开发维稳体系上,首次引入的了自动化测试+git hook的方案来保证提交的代码必定是不会出问题的。
其余同窗提交的代码在push的时候会触发本地prepush hook并进行直出页面的自动化测试,只有经过自动化测试才能够提交代码。
这个方案极大的保证了直出服务的稳定,自此方案上线以来,再无直出服务出现问题状况发生~
应用型技术的难点不是在克服技术问题(由于大部问题都是有解决方案的),而是在于可以不断的结合自身的产品体验,发现其中存在的体验问题,不断使用更好的技术方案去优化用户的体验,为整个产品发展添砖加瓦。
作为公司最大的同构直出服务实践,在后续的方案中,咱们会进一步着手优化用户的使用体验。好比使用服务器缓存等手段来进一步减小服务器端的耗时,优化直出图片的加载的体验等等,同时会更多丰富的实战经验分享给你们。
此文已由做者受权腾讯云技术社区发布,转载请注明文章出处
原文连接:https://cloud.tencent.com/community/article/531340