歡迎你們前往騰訊雲技術社區,獲取更多騰訊海量技術實踐乾貨哦~
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