本文分享來自淘系內容前端團隊-蒂夫
在開始正題以前,我先講一個內容詳情的業務場景和其面臨的性能問題前端
圖文內容詳情業務自己有三個比較大的特色:web
那咱們看這麼一個頁面的加載和渲染過程大概是什麼樣的?算法
從上面的流程能夠看出幾個問題:小程序
這時候咱們要結合業務自己的特性進行一些大膽的思考:瀏覽器
首先想到的是在研發過程當中就對內容進行預渲染並存儲起來,可是這個方案很快被 pass 了,有兩個主要緣由:緩存
既然數據絕大部分是靜態化的,爲何不能把用戶訪問時靜態數據和代碼渲染後的結果進行靜態化,這樣不是省去了 renderToHTML 的過程了嗎?,以下圖所示安全
那講到這裏,咱們首先想到的是經過 SSR renderToHTMLString,而後把渲染後的結果進行緩存,這樣訪問到相同內容的請求能夠直接將緩存結果返回,那這樣有什麼好處呢:服務器
可是同時也帶來了其餘的問題:markdown
這時候咱們就在想,若是可以將渲染的結果或者渲染的過程放在 CDN 上就行了,由於 CDN 節點比較多,而且在世界範圍部署普遍,因此咱們嘗試着將 SSR 渲染的結果存儲在 CDN 上,可是隨之帶來另一些問題:weex
這時咱們正好了解到了 CDN 正在推廣一種邊緣計算的能力(EdgeRoutine,下面會作簡單的介紹),簡單點理解就是能夠在 CDN 請求返回結果以前加上你的自定義腳本,而且能夠訪問 CDN 的數據,那就意味着咱們能夠控制 CDN 請求返回的內容或者HTTP 狀態,好像基本可以解決我上面說的兩個問題,因此按照當前的技術能力和咱們的需求咱們針對請求鏈路進行了改造:
具體的降級和緩存清除的邏輯沒有畫出來,由於那是解決安全生產的問題,我主要想強調方案調整帶來的性能提高。
因此從上圖能夠看出,一個正常的請求首先會請求到 CDN,CDN 若是發現緩存中沒有的話會回源到 SSR 服務器,這樣首屏其實只須要一個網絡請求,有效的提高的首屏性能和下降了服務器壓力。細心的你會發現頁面首屏後還進行了一次請求動態數據的動做,由於還有一個對實時性要求比較高的數據須要展現給用戶,可是並不影響用戶瀏覽,另外雖然內容不怎麼會更新但也會存在更新的狀況,因此咱們會在瀏覽器端作一次緩存的時間和內容最新更新時間的對比,若是發現不一致,會主動作緩存更新,這樣,既保證了性能,又避免了緩存過時
經過作如上的方案咱們在性能,業務指標提高,服務器壓力上都有很大的收穫
關於邊緣計算,你們能夠參考:developer.aliyun.com/article/757…
本篇文章貼出幾張核心的圖供你們參考:
簡單總結就是咱們能夠在 CDN 返回結果以前進行一些邏輯計算,而且這部分代碼兼容 ES6 的規範,而且能夠經過 HTTP 和外界服務進行溝通,達到有效的控制的 CDN 返回的表現的目的
在此我想重點介紹下邊緣計算的共享優點,對於邊緣計算來講,它不只能夠處理一些邏輯計算,還能夠將計算的結果進行存儲,存儲能力是 Swift 的 Open API ,實現數據的 KV 存儲,這就意味着,這個存儲空間能夠很是大。說道這裏你們可能會感受比較抽象,能夠看下面這張圖,上面是指咱們正常的網絡請求,用戶手機直連數據服務器和頁面 CDN,這就意味每一個人都要經歷加載頁面,加載數據,渲染頁面等邏輯。下面是指 CDN ER 作了一層代理,這就意味着用戶手機連接 CDN,CDN 負責和數據服務器和頁面 CDN 進行溝通。那樣這樣有什麼好處呢,這就意味着咱們能夠將像內容詳情這種數據或者渲染的結果直接存儲在 CDN 上,而且不用擔憂存儲內容太多影響性能,這就就像一羣人公用一部手機,你看完傳遞給下一我的刷新看相同的內容
既然能在 CDN 的 ER 節點上寫 ES6 的代碼,而且能夠請求數據,這就意味着咱們能夠在ER上執行不少邏輯,在這裏我整理一些經常使用的:
那基於這些能力咱們還能支持哪些合適的場景落地呢,因此咱們針對淘系的場景進行了調研
總體調研有一個統一的思路,就是要找適合靜態化的高流量場景,就是說頁面是否有可被緩存的數據或者渲染結果,爲此咱們整理了一個簡單的表格:
接下來我作一些簡單的說明:
總結一下標準的頁面請求過程以下:
這裏說一下,其實在數據側有不少靜態化策略已經被用的遊刃有餘,例如藉助於 CDN、Tair、OSS,若是咱們可以讓靜態化的過程變得更加簡單和通用,例如將數據或者頁面渲染結果直接存儲在 CDN,下次請求就能夠直接複用渲染結果,有沒有可能變成以下模式:
其實就是兩個原則:
最終結合 ER 的能力和咱們的業務場景,咱們抽象爲如下四種:
通過咱們的測試和實踐,針對前三種產出了一些性能報告也能夠分享給你們,雖然不全面,可是能說明問題,因爲測試的頁面場景不同,因此相互(數據預加載、 ESR、SSR 靜態化)沒有必要做對比,如下指標是相對沒有使用 TESI 的能力進行的對比
雖然 ER 有這麼多優點,可是接入成本仍是比較大的,例如要注意 ER 容器自己的各類限制、調試成本、雲資源申請成本等,因此咱們須要提供一種標準的接入方式,初步瞭解到 W3C 有一個 ESI 的標準,維基百科介紹以下:
簡單翻譯一下:ESI 是一種邊緣級 web 動態化的小型標記語言。ESI 的目的是解決 web 基礎設施的擴展問題。它是邊緣計算的一種應用方案
原理如圖:
其實就是說,能夠經過標籤注入的方式,實現動靜內容混合混合輸出,比較符合咱們的訴求,而且其在語法上也比較豐富
但問題是 ESI 是一種 XML 的標準,阿里有不少頁面資源類型並非 HTML,例如 weex、小程序等等,它們加載的頁面並非 HTML,而且咱們要知足標準化場景的接入,因此咱們須要在 ESI 的基礎上進行改造-TESI(Taobao Edge Side Includes),合適的纔是最好的
基本的代碼形式如何,咱們以數據預加載爲例,以下 H5 中出現 TESI 標籤(鼠標選中部分)
TESI 標籤描述了一個 http 接口的信息,而且配置了其緩存時長 s-maxage,ER 會解析這個標籤,而且在 ER 上發起請求,並將請求的數據按照 s-maxage 配置的值進行緩存,這就意味着下一次請求到相同的節點,就會直接返回緩存結果
渲染結果以下:
咱們看其實像數據預加載這種狀況,在 HTML 中會渲染成一個 script 標籤,其中存儲的是一個全局變量方便運行時獲取。
其實 TESI 標籤不只能夠用於 HTML 中,JS 中能夠出現 TESI 標籤,以下:
渲染後
其基本渲染原理以下,比較簡單,這裏不作贅餘:
還有其餘幾種類型的標籤以下:
標籤名 |
描述 |
tesi:data |
數據預加載 |
tesi:esr |
邊緣渲染 |
tesi:ssr |
SSR 靜態化 |
tesi:redirect |
邏輯跳轉 |
tesi:include |
區塊引入 |
整個 ER 執行的過程會遇到各類各樣的問題,甚至 ER 都有掛掉的風險,因此須要有穩定降級的預案保證不影響用戶,因此咱們會將 CDN 源站指向頁面 CDN 的源站,這樣,及時 ER 解析出現問題,能夠把解析前的頁面直接返回給瀏覽器
ER 提供了兩種緩存:內存緩存(如下簡稱 Cache)和 Swift KV 緩存(如下簡稱 KV),這兩種模式在存取速度、體積大小、QPS 上都有差異,總結基本以下:
指標 |
VS | 勝出 |
存儲空間 |
Cache ﹤ KV |
KV,可達 幾十 GB |
QPS |
Cache ﹥ KV |
Cache |
存取速度 |
Cache ﹥ KV |
Cache |
存儲反作用 |
Cache ﹥ KV |
KV |
這裏指的存儲反作用是指,存儲大小對於 ER 性能的影響,存儲在緩存中,若是存儲體積接近內存大小,首先會影響 ER 執行性能,嚴重會致使 ER 容器重啓
綜合以上兩種對比結果來看各有千秋,但合適的場景用合適的模式纔是最好,爲此咱們設計了二級緩存模式,一級緩存存入內存,二級緩存存入 KV,主要完成以下三個重點邏輯:
緩存的內容須要具有快速清除的能力,由於數據會更新、頁面 bundle 會更新,特別是遇到緊急狀況,例如線上問題緊急修復,須要可以實現緩存及時清除,因此須要必定的策略來知足需求,整體清除的邏輯會依賴請求,根據標籤的身份信息進行清除
爲了知足系統穩定性和安全生產的要求,TESI 標籤的生產過程是須要被管控起來的,因此咱們要提供一個 TESI 的運維繫統主要知足如下幾個需求:
運維繫統使用過程以下:
運維繫統主要爲了生成一個可用的 TESI 標籤,真正發佈生效咱們會藉助於 DEF 發佈系統,這樣既沿用了標準,安全生產相關能力咱們也不用重複建設了,基本流程以下:
其基本的配置信息和執行過程以下,你們能夠參考下:
仍是亙古不變的話題,若是你對邊緣計算感興趣,歡迎加入咱們共同建設,若是你對咱們的業務感興趣,也歡迎你的加入,業務瞭解:zhuanlan.zhihu.com/p/128956855
簡歷投遞:yabing.zyb@alibaba-inc.com
主題備註:前端-公司-名字