我所在的部門是阿里巴巴國際站,國際站是一個全球 B 類跨境貿易平臺,平臺服務的買賣家是來自於全球一百多個國家和地區,這些國家和地區的發展和貿易環境差別較大例如:一些亞非拉地區的網絡環境和終端設備較差,網站性能在這些地區堪憂。同時因爲 B 類貿易較 C 類更爲嚴肅,很大部分地區(歐美)更習慣使用 WEB,網站很大部分流量來自於 SEO,針對於這種狀況,咱們須要支持服務端渲染,來提升首屏性能,保證用戶訪問網站的體驗和提高 SEO 流量。(下圖是客戶端渲染 CSR 和 服務端渲染 SSR 的首屏渲染差別)
javascript
互聯網發展之初沒有前端這一角色,早期的頁面基本都是由後端一把梭.隨着 ajax 的誕生,而替代傳統 web 交互模式的 ajax 技術使得動態更新網頁變成可能,因爲頁面交互的複雜度提高,基於專業性的要求致使先後端的分工,前端開始承接視圖的開發工做。但頁面容器仍是由傳統的 PHP 和 java 等去承載:php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
<link rel="stylesheet" type="text/css" href="xxx">
</head>
<body>
<?php $searchQuery = $_GET['q']; ?>
<h1>You searched for: <?php echo $searchQuery; ?></h1>
<p>this is a demo</p>
</body>
</html>
複製代碼
相似還有 ASP、JSP 等腳本語言,將頁面的動態內容嵌入到頁面中。css
隨着 13 年 react 的出現併火熱,其基於 vdom 的設計使UI邏輯和渲染分離,可以將一套代碼多端渲染,至此拉開了前端框架的 SSR 之路,同時這一階段隨着前端工程化的不斷髮展,爲了提高前端開發效率,傳統的異構渲染服務已經成爲開發效率的瓶頸。同時隨着Sam Newman 提出了 Backends For Frontends ,根據服務自治的原則,前端爲了開發的靈活性,開始承接整個 UI 層(接口和頁面 SSR)
html
雖然 BFF 在業界和阿里有較多的實踐和沉澱但仍是侷限在一些特定領域。如阿里不少前臺場景已有多年的 java 服務運行穩定,改形成本較高且風險大,落地不太現實,最終仍是在一些內部效能平臺,賣家平臺等場景有較多落地。同時加一層 BFF 還有如下一些挑戰:
前端
回頭來看咱們須要 SSR 的場景,大多都是流量較大,直面大量用戶(買家)的場景,而這些業務場景不少只是部分頁面須要有 SSR 的能力,同時前端沒有能力所有使用 BFF 來承接流量。在這個過程當中,咱們也探索和實踐其餘方案:java 的js 引擎 nashorn 來提供 SSR 渲染,最後發現總體性能遠低於 node ;同機部署 node 來支持 SSR ,同機部署和 java 進程的資源競爭和運維複雜度都帶來了挑戰。
基於以上緣由,咱們在思考,能不能把渲染能力做爲一個服務,須要渲染模板(組件)直接調用返回 html 片斷便可,不須要對業務應用侵入性這麼強。java
肯定了問題後,渲染服務就須要提供一種輕量級的接入方式來知足不通場景的渲染訴求。那咱們就須要知足如下訴求:node
{
app: "silkworm-render-demo", // 應用名
loader: "webpack", // 加載方式
module: "xxx/pages/demo/index.js", // 資源入口
deps: [], // 依賴資源
version: "0.0.1",
engine: "react", // 渲染引擎
props: { // 渲染數據
count: 100,
},
}
複製代碼
當業務方調用咱們的渲染服務後,咱們根據資源描述信息從 CDN 或者靜態資源服務器上拉取資源到服務器本地,而後根據不一樣的加載器執行文件的加載和解析,以後根據用戶選擇的渲染引擎,執行不一樣的渲染邏輯。整個架構圖以下:
react
在保障性能和穩定性上:咱們針對資源作了本地文件和內存級別的緩存策略,同時針對不通頁面渲染作了基於 vm 沙箱的渲染隔離,提供單獨的上下文兼容 window 等客戶端書寫問題,同時作了超時處理webpack
渲染服務上線後,咱們支持了六七條業務線,囊括了大部分買家場景的渲染訴求,同時也支持了郵件推送的訴求(離線渲染郵件模板觸達用戶),總體運行平穩,主要效果仍是在於如下幾點:web
但渲染服務實際上是一箇中心化服務,隨着應用的接入愈來愈多,中心化的風險愈來愈高,接入方的聲音也愈來愈多:
無服務器架構(Serverless architectures)是指一個應用大量依賴第三方服務(後端即服務,Backend as a Service,簡稱「BaaS」),或者把代碼交由託管的、短生命週期的容器中執行(函數即服務,Function as a Service,簡稱「FaaS」)。serverless 潛在的幾個優勢:
以 web 應用爲例,如下是一個 serverless web 的架構:
針對 serverless 的特色,結合以前提到的咱們渲染服務在伸縮性上和渲染容器的隔離性上的不足,咱們開始着手改造將渲染服務遷移至 FaaS 平臺,在遷移前咱們評估了渲染服務和 FaaS 在如下幾點十分契合:
在遷移以後,咱們藉助 FaaS 的擴縮容能力,能夠有效的提升資源利用率,下降成本,同時下降了郵件離線渲染對在線渲染服務的影響。
那除了已有的頁面想快速接入 SSR 能力,像已有的 BFF 應用,或者但願能借助 serverless 來承接整個 UI 層的前端,咱們該如何藉助 FaaS 來改造升級。
以 BFF 應用爲例,咱們前端承接了客戶端的開發同時也負責服務端視圖層接口的開發工做,咱們能夠將服務端的接口以 FaaS 形式進行部署,減小運維成本。而須要 SSR 的頁面咱們也發佈到 FaaS 平臺,以 page as function 的形式進行部署,開發和構建以下:
# 應用的網關配置
basePath: /demo
appName: demo
# 應用的路由
router:
render:
# 頁面內容ssr
path: /render/home-list ## 頁面內容 ssr
method: get
parameters:
- name: page
in: path
# 接口的具體實現
integration:
type: function
uri: render
getPage:
# 頁面的 http
path: /home
method: get
integration:
type: template
uri:
body:
$ref: template/index.ejs
複製代碼
最終,咱們的用戶請求 SSR 渲染路徑以下:
除了現有的 FaaS 平臺外,像阿里雲提供的 EdgeRoutine(ER):支持在CDN邊緣執行客戶編寫/編譯的JavaScript(WebAssembly),也能夠支持 SSR 在邊緣節點進行渲染,可以更快的返回給用戶頁面內容。
因爲自己邊緣節點對運行耗時有更短的限制,同時受限的 js 運行時(service worker)對 node 開發不太友好,並且邊緣節點渲染比服務端渲染優點沒有那麼明顯,暫時不太建議在邊緣節點上作 SSR。
可是咱們能夠經過將頁面容器發佈到邊緣節點,當用戶訪問時上將頁面非 SSR 部分流式返回給用戶,同時向服務端發起 SSR 請求,最終返回給用戶頁面。這樣好處是:讓用戶更早看到頁面部份內容,同時資源的請求和 SSR 渲染是並行執行,能更快的提高首屏速度。
以上升級改造在進行中
回到 SSR 初衷,本質上咱們仍是爲了給用戶更好的交互體驗,如更好的性能。隨着 serverless 相關技術的不斷髮展,更輕量級的業務開發變成可能,須要咱們更深刻業務場景,提供貼合深刻的體驗優化。共勉