當 SSR 趕上 FaaS

前言

爲何 SSR ?

我所在的部門是阿里巴巴國際站,國際站是一個全球 B 類跨境貿易平臺,平臺服務的買賣家是來自於全球一百多個國家和地區,這些國家和地區的發展和貿易環境差別較大例如:一些亞非拉地區的網絡環境和終端設備較差,網站性能在這些地區堪憂。同時因爲 B 類貿易較 C 類更爲嚴肅,很大部分地區(歐美)更習慣使用 WEB,網站很大部分流量來自於 SEO,針對於這種狀況,咱們須要支持服務端渲染,來提升首屏性能,保證用戶訪問網站的體驗和提高 SEO 流量。(下圖是客戶端渲染 CSR  和 服務端渲染 SSR 的首屏渲染差別)
javascript

前端 SSR 歷程

傳統的異構服務端渲染

互聯網發展之初沒有前端這一角色,早期的頁面基本都是由後端一把梭.隨着 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

BFF 架構前端接管 UI 層

隨着 13 年 react 的出現併火熱,其基於 vdom 的設計使UI邏輯和渲染分離,可以將一套代碼多端渲染,至此拉開了前端框架的 SSR 之路,同時這一階段隨着前端工程化的不斷髮展,爲了提高前端開發效率,傳統的異構渲染服務已經成爲開發效率的瓶頸。同時隨着Sam Newman 提出了 Backends For Frontends ,根據服務自治的原則,前端爲了開發的靈活性,開始承接整個 UI 層(接口和頁面 SSR)
html

BFF SSR 困境

雖然 BFF 在業界和阿里有較多的實踐和沉澱但仍是侷限在一些特定領域。如阿里不少前臺場景已有多年的 java 服務運行穩定,改形成本較高且風險大,落地不太現實,最終仍是在一些內部效能平臺,賣家平臺等場景有較多落地。同時加一層 BFF 還有如下一些挑戰:
前端

渲染服務

爲何提供渲染服務

回頭來看咱們須要 SSR 的場景,大多都是流量較大,直面大量用戶(買家)的場景,而這些業務場景不少只是部分頁面須要有 SSR 的能力,同時前端沒有能力所有使用 BFF 來承接流量。在這個過程當中,咱們也探索和實踐其餘方案:java 的js 引擎 nashorn 來提供 SSR 渲染,最後發現總體性能遠低於 node ;同機部署 node 來支持 SSR ,同機部署和 java 進程的資源競爭和運維複雜度都帶來了挑戰。
基於以上緣由,咱們在思考,能不能把渲染能力做爲一個服務,須要渲染模板(組件)直接調用返回 html 片斷便可,不須要對業務應用侵入性這麼強。java

渲染服務實現

肯定了問題後,渲染服務就須要提供一種輕量級的接入方式來知足不通場景的渲染訴求。那咱們就須要知足如下訴求:node

  1. 低成本開發&部署:咱們指望同構的代碼不須要進行較大的改造,最好是零改造就能夠直接支持渲染能力。
  2. 多模板引擎支持:因爲不通業務方對渲染有本身的自定義訴求,除了單純的 react 渲染之外,支持移動端的 rax 渲染,支持搭建場景的基於特定 schema 的渲染訴求。
    針對以上訴求,咱們提供了一個 RPC 服務,只要調用方提供相應的渲染基礎信息和渲染數據,咱們便可返回渲染結果,配置信息以下:
{
  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

  1. 接入成本低:前端應用(react)基本沒有改形成本,只須要後端調用渲染服務回填頁面便可。
  2. 穩定性
  • 非侵入:服務做爲能力提供方,即便出現問題也不影響業務應用的運行。
  • 運維保障:提供了統一的監控、日誌、錯誤處理能力,來保障渲染穩定性。

但渲染服務實際上是一箇中心化服務,隨着應用的接入愈來愈多,中心化的風險愈來愈高,接入方的聲音也愈來愈多:

  • 頁面流量大了會不會撐不住?
  • 應用多了會不會互相影響?
  • 能不能定製加載策略、緩存優化?

    雖然渲染服務下降了接入和運維的成本,可是還有一些問題沒法解決:
  • 彈性擴容
  • 容器隔離
  • 靈活定製

SSR 趕上 FaaS

serverless 簡介

無服務器架構(Serverless architectures)是指一個應用大量依賴第三方服務(後端即服務,Backend as a Service,簡稱「BaaS」),或者把代碼交由託管的、短生命週期的容器中執行(函數即服務,Function as a Service,簡稱「FaaS」)。serverless 潛在的幾個優勢:

  • (理論上)無限可用的計算資源
  • 用戶不再須要承擔服務器運維的工做和責任
  • 服務的按需付費成爲可能
  • 超大型數據中心的使用成本顯著下降
  • 經過資源虛擬化管理,運維操做的難度大大下降
  • 得益於分時複用,物理硬件的利用率大大提升

以 web 應用爲例,如下是一個 serverless web 的架構:

渲染服務遷移 FaaS

針對 serverless 的特色,結合以前提到的咱們渲染服務在伸縮性上和渲染容器的隔離性上的不足,咱們開始着手改造將渲染服務遷移至 FaaS 平臺,在遷移前咱們評估了渲染服務和 FaaS 在如下幾點十分契合:

  • 基於事件觸發是非長連接的服務處理
  • 渲染自己是純粹的 CPU 計算,無狀態服務
  • FaaS 的伸縮性很適合調用量波動較大的渲染服務
    基於以上的特色,咱們將不通的引擎進行 FaaS 部署提供對外服務,總體架構以下:

在遷移以後,咱們藉助 FaaS 的擴縮容能力,能夠有效的提升資源利用率,下降成本,同時下降了郵件離線渲染對在線渲染服務的影響。

FaaS SSR 一體化應用

那除了已有的頁面想快速接入 SSR 能力,像已有的 BFF 應用,或者但願能借助  serverless 來承接整個 UI 層的前端,咱們該如何藉助 FaaS 來改造升級。
以 BFF 應用爲例,咱們前端承接了客戶端的開發同時也負責服務端視圖層接口的開發工做,咱們能夠將服務端的接口以 FaaS 形式進行部署,減小運維成本。而須要 SSR 的頁面咱們也發佈到 FaaS 平臺,以 page as function 的形式進行部署,開發和構建以下:


當頁面和接口發佈後咱們經過網關層,來路由頁面到不一樣的頁面模板和 function 來提供 SSR 渲染和接口服務。

# 應用的網關配置
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 相關技術的不斷髮展,更輕量級的業務開發變成可能,須要咱們更深刻業務場景,提供貼合深刻的體驗優化。共勉

相關文章
相關標籤/搜索