開發與維運

前端新思路:組件即函數和Serverless SSR實踐

image.png

在今天,對於Node.js運維和高併發依然是很有挑戰的,為了提效,將架構演進為頁面即服務,可是粒度還不夠,藉著雲原生和Serverless大潮,無運維,輕鬆擴展,對前端是極大的誘惑。那麼,基於FaaS之上,前端有哪些可能性呢?

2019年上半年,我在阿里巴巴經濟體前端委員會推進的Serverless研發體系共建項目中負責Serverless SSR的研究,將CSR,SSR,邊緣渲染進行整合和嘗試,提出組件即服務的概念(Component as Service),試圖結合FaaS,做出更簡單的開發方式。本次分享主要圍繞Serverless SSR和它的演進過程、背後思考為主。

本文是狼叔在D2大會的分享《前端新思路:組件即服務和Serverless SSR實踐》的內容,閱讀需要10分鐘,你講了解如下內容。

  1. 可以瞭解Serverless時代端側渲染面臨的具體問題
  2. 可以瞭解Serverless SSR規範以及渲染體系的完整工作鏈路和原理
  3. 為業內提供解決Serverless SSR渲染問題的新思路

image.png

狼叔(網名i5ting)現為阿里巴巴前端技術專家,Node.js 技術佈道者,Node全棧公眾號運營者,曾就職於去哪兒、新浪、網秦,做過前端、後端、數據分析,是一名全棧技術的實踐者。目前負責BU的Node.js和基礎框架開發,已出版《狼書(卷1) 更了不起的Node.js》。

Wolfred Sang (a.k.a. i5ting) is a full-stack developer and Node.js evangelist. He works for Alibaba Group as a Principal Front-End Developer and runs a self-media on the topic of Full-stack Node.js. He worked for well-known dot-com companies in the past, such as Qunar, Sina and NQ Mobile. His expertise and experiences cover not only front-end development but also back-end engineering and data insight extracting. He is currently leading the Node.js development and maintaining the core codebase in his business unit. His book “The Marvelous Node.js” (Part I) was published in July 2019.

技術趨勢分析

image.png

今年的技術趨勢,我的判斷是技術混亂期已過,提效才是今日的挑戰。

image.png

在Node.js領域,今年新東西也不多,最新已經發布到13,lts是12,Egg.js的生態持續完善,進度也不如前2年,成熟之後創新就少了。在很多框架上加入ts似乎已經政治正確了。比如自身是基於ts的nest框架,比如阿里也開源了基於Egg生態的midway框架,整體加入ts,類型系統和oop,對大規模編程來說是非常好的。另外GraphQL也有很強的應用落地場景,尤其是Apollo項目帶來的改變最大,極大的降低了落地成本。已經用rust重寫的deno穩步進展中,沒有火起來,但也有很高的關注度,它不會替代Node.js,而是基於Node之上更好的嘗試。

今日的Node.js存在的問題是會用很容易,做到高可用不容易,畢竟高可用對架構運維要求更好一些,這點對前端同學要求會更難一些。做到高可用之後,做性能調優更難。其實,所有這些難點的背後是基於前端工程師的角度來考慮的,這也是非常現實的問題。

對於很多團隊,上Node是找死,不上Node是等死。在今天web框架已經相當成熟,所以如何破局,是當下最破解需要解決的問題。

你可能會感覺Node.js熱度不夠,但事實很多做Node.js的人已經投身到研發模式升級上了。對於今天的Node.js來說,會用很容易,但用好很難,比如高可用,性能調優,還是非常有挑戰的。我們可以假想一下,流量打網關,網關根據流量來實例化容器,加載FaaS運行時環境,然後執行對應函數提供服務。在整個過程中,不許關心服務器和運維工作,不用擔心高可用問題,是不是前端可以更加輕鬆的接入Node.js。這其實就是當前大廠前端在做的前端基於Serverless的實踐,比如基於FaaS如何做服務編排、頁面渲染、網關等。接入Serverless不是目的,目的是讓前端能夠藉助Serverless創造更多業務價值。

為何如此鍾愛SSR?

在2017年底,優酷只有passport和土豆的部分頁面用Node.js,QPS不高,大多是一些嘗試性業務,優酷PC和H5核心頁面還都是PHP模板渲染。最近2年基於阿里強大的技術體系,我們也對PC、H5多端進行了技術改造。今年雙十一是React SSR第一次扛雙十一,具有一定意義,這裡簡單總結回顧和展望。

背景就不贅述了,參見《這!就是優酷Node.js重構之路》

image.png

  • 將優酷C端核心頁面全部用Node重寫,完成了PHP到Node.js的遷移。在沒有PHP同學的情況下,前端可以支撐業務。
  • 性能提升明顯,從v1(Bigpipe+jQuery)到v2(React SSR),性能逐步提升。PC頁面首屏渲染降到150ms、播放器起播時間從4.6秒優化到2秒。H5站上了React SSR後,性能提升3倍,H5喚端率提升也極其明顯,頭條短視頻喚端率由5.68%提升到9.4%,環比提升65%。單機性能qps從80提升150+(壓測最高可以到300左右)。
  • QPS過萬,2年沒有p4以上故障,相對來說是比較穩定的。扛過雙十一、世界盃,最高三倍以上的流量。
  • 在集團前端委員會承擔Serverless SSR專項。

裕波曾經問我,為何如此鍾愛SSR?

從前端的角度看,它是一個相對小的領域。PC已經非主流,H5想爭王者,卻不想被rn、weex中間截胡。怎麼看,SSR能做的都有限。但是,用戶體驗提升是永遠的追求,另外web標準化是正統,在二者之間,和Node做結合,除了SSR,目前想不到更好的解法。

貼著C端業務,從後端手裡接過來PC、H5,通過Node構建自己的生存之地是必然的選擇。

活下來之後就開始有演進,沉澱,通過C端業務和egg-react-ssr開源項目的沉澱,我們成功的打通2點。

  1. 寫法上的統一:CSR和SSR可以共存,繼而實現二種模式的無縫切換
  2. 容災降級方案:從Node SSR無縫切換到Node的CSR,做到第一層降級,從Node CSR還可以繼續降到CDN的CSR

2019年,另外一個風口是Serverless,前端把Serverless看成是生死之地,下一代研發模式的前端價值證明。那麼,在這個背景下,SSR能做什麼呢?基於FaaS的SSR如何呢?繼續推演,支持SSR,也可以支持CSR,也就是說基於FaaS的渲染都可以支持的。於是和風馳商量,做了Serverless端側渲染方向的規劃。

本來SSR是Server-side render,演進為Serverless-side render。元彥給了一個非常好概念命名,Caaf,即Component as a fuction。渲染層圍繞在以組件為核心,最終統統簡化到函數層面。

在今天看,SSR是成功的,一個曾經比較偏冷的點已經慢慢變得主流。集團中,基於React/Rax的一體化開發,可以滿足前端所有開發場景。優酷側的活動搭建已經升級到Rax1.0,對外提供SSR服務。在uc裡,已經開始要將egg-react-ssr遷移到FaaS上,代碼已經完成遷移。

  • PC/中後臺,React的CSR和SSR
  • 移動端/H5,Rax的CSR和SSR。尤其是Rax SSR給站外H5提供了非常好的首屏渲染時間優化,對C端或活動支持是尤其有用的。

在2020年,基於FaaS之上的渲染已經獲得大家的認可。另外大量的Node.js的BFF應用已經到了需要治理的時候,BFF感覺和當年的微服務一樣,太多了就會牽扯到管理成本,這種情況下Serverless是個中臺內斂的極好解決方案。對前端來說,SSR讓開發變得簡單,基於FaaS又能很好的收斂和治理BFF應用,結合WebIDE,一種極其輕量級基於Serverless的前端研發時代已經來臨了。

Serverless-side render概念升級

從BFF到SFF

瞭解SSR之前,我們先看一下架構升級,從BFF到SFF的演進過程。

BFF即 Backend For Frontend(服務於前端的後端),也就是服務器設計 API 時會考慮前端的使用,並在服務端直接進行業務邏輯的處理,又稱為用戶體驗適配器。BFF 只是一種邏輯分層,而非一種技術,雖然 BFF 是一個新名詞,但它的理念由來已久。

在Node.js世界裡,BFF是最合適的應用場景。常見的API、API proxy、渲染、SSR+API聚合,當然也有人用來做網關。

從Backend For Frontend升級Serverless For Frontend,本質上就是利用Serverless基建,完成之前BFF的工作。那麼,差異在哪裡呢?

image.png

核心是從Node到FaaS,本質上還是Serverless,省的其實只是運維和自動擴縮容的工作,一切看起來都是基建的功勞,但對於前端來說,卻是極為重大的痛點解決方案,能夠滿足所有應用場景,基於函數粒度可以簡化開發,乃生死必爭之地。

SFF前後端分工

Serverless簡單理解是FaaS+BaaS。FaaS是函數即服務,應用層面的對外接口,而BaaS則是後端即服務,更多的是業務系統相關的服務。當下的FaaS還是圍繞API相關的工作為主,那麼,前端如何和Serverless綁定呢?

Serverless For Frontend(簡稱SFF)便是這樣的概念,基於Serverless架構提供對前端開發提效的方案。

下面看一下SFF分工,這張圖我自認為還是非常經典的。首先將Serverless劈成2半,前端和後端,後端的FaaS大家都比較熟悉了,但前端頁面和FaaS如何集成還是一片待開發的新領域。

image.png

舉個例子,常見BFF的例子,hsf調用獲得服務端數據,前端通過ctx完成對前端的輸出。這時有2種常見應用場景

  1. API,同後端FaaS(RPC居多)
  2. 頁面渲染(http居多)

基於FaaS的頁面渲染對前端來說是必須的。從beidou、Next.js、egg-react-ssr到Umi SSR,可以看出服務端渲染是很重要的端側渲染組成部分。無論如何,React SSR都是依賴Node.js Web應用的。那麼,在Serverless時代,基於函數即服務(Functions as a Service,簡寫為FaaS)做API開發相關是非常簡單的。

1)無服務,不需要管運維工作
2)代碼只關係函數粒度,面向API變成,降低構建複雜度
3)可擴展

image.png

目前還是Serverless初期,大家還是圍繞API來做,那麼,在FaaS下,如何做好渲染層呢?直出HTML,做CSR很明顯是太簡單了,對於React這種高級玩法如何集成呢?

其實我們可以做的更多,筆者目前能想到的Serverless時代的渲染層具有如下特點。

  • 採用Next.js/egg-react-ssr寫法,實現客戶端渲染和服務端渲染統一
  • 採用Umi SSR構建,生成獨立umi.server.js做法,做到渲染
  • 採用Umi做法,內置Webpack和React,簡化開發,只有在構建時區分客戶端渲染和服務端渲染,做好和CDN如何搭檔,做好優雅降級,保證穩定性
  • 結合FaaS API,做好渲染集成。 為了演示Serverless下渲染層實現原理,下面會進行簡要說明。在Serverless雲函數裡,一般會有server.yml作為配置文件,這裡以lamda為例子。

image.png

SSR概念升級

現狀

  1. 現有FaaS主要是針對API層做擴展,視圖渲染是一個新的命題。
  2. 競品Serverless.com提供了Components類似的視圖渲染層方案
  3. 如何打造一個基於阿里技術棧又有業界領先的端側渲染解決方案

業界還缺少最佳實踐,這是極好的機會。因此,我們對SSR做了概念上的升級(感謝justjavac大佬的提示)

image.png

Serverless端渲染層,是針對 SSR 做概念和能力升級

  • SSR 從 Server side render 升級為 Serverless side render,基於FaaS環境,提供端側頁面渲染能力。
  • Serverless渲染層涵蓋的範圍擴展,從服務器端渲染升級到同時支持 CSR 和 SSR 2種渲染模式。

在 Serverless 背景下,頁面渲染層包含2種情況。

  • 基於 FaaS 的客戶端渲染
  • 基於 FaaS 的服務器端渲染

目標是提供基於 FaaS 的頁面渲染描述規範,提供標準化組件描述,統一組件寫法,用法簡單,易實現,可擴展。因此,我們制定了SSR-spec規範,下面會詳細講解。

Serverless-side render規範和實現原理

在講規範之前,我們先簡單瞭解3個術語。

名稱 英文 簡寫 描述
客戶端渲染 client-side render CSR 通過React、Rax編寫的組件,打包構建後,以html文件形式分發到CDN上,不需要Node.js支持
服務器端渲染 Server-side render SSR 通過React、Rax編寫的組件,打包構建後,分別生成server bundle和client bundle,其中server bundle由Node.js服務端寫入到瀏覽器,client bundle分發到CDN上,採用混搭的寫入渲染方式,來提高首屏渲染效率。
組件即函數 Component as a function Caaf 組件即函數,在Serverless領域以函數為核心,在渲染也是已函數為核心,做到資源與函數分類,實現渲染層獨立,為開發提供更大家便利。

CSR和SSR

先科普一下CSR和SSR的概念
客戶端渲染(簡稱CSR),簡單理解就是html是沒有被動態數據灌入的,即所謂的靜態頁面。比如通過React、Rax編寫的組件,打包構建後,以html文件形式分發到CDN上,不需要Node.js支持,就是非常典型的CSR。

image.png

資源加載完成後(注意:只有一個bundle,一次性吐出),通過React中的render API進行頁面渲染。優點是不需要服務端接入,簡單,對於性能要求不高的頁面是非常合適的。中後臺應用大多是CSR,優化也都是打包環節玩。

服務器端渲染(SSR),簡單理解就是html是由服務端寫出,可以動態改變頁面內容,即所謂的動態頁面。早年的php、asp、jsp這些Server page都是SSR的。但基於React技術棧,又有些許不同,server bundle構建的 時候,要吐多少模塊,是server端決定的。client bundle和之前一樣,差別在於這次是hydrate,而非render。

image.png

hydrate是 React 中提供在初次渲染的時候,去複用原本已經存在的 DOM 節點,減少重新生成節點以及刪除原本 DOM 節點的開銷,來加速初次渲染的功能。主要使用場景是服務端渲染或者像prerender等情況,所以在圖中hydrate之後才是tti時間。

如果想全局瞭解CSR和SSR,共分5個階段,參考下圖。

image.png

純服務端渲染和純客戶端渲染是2個極端,React SSR是屬於中間的,這種服務端吐出的粒度是可以根據業務來控制的,可以服務端多一點,性能會差,也可以服務端吐出剛好夠首屏的數據,其他由客戶端來處理,這種性能會好很多。Static SSR和預渲染的CSR也是特定場景優化的神器。

最佳寫法

瞭解了CSR和SSR的區別,下面我們看一下最佳寫法是如何演進的。業內最好的實現大概是next.js了,拋開負責度不談,單就寫法來說,它確實是最合理的。

既然是基於React做法,核心肯定以Component為主,在Component上擴展靜態方法用於接口請求是很好的實踐。寫法如下。

image.png

早年寫過bigpipe相關事項,其中模塊成為biglet,它的作用是獲取接口,結合tpl生成html。

image.png

biglet的生命週期如下。

before
.then(self.fetch.bind(self))
.then(self.parse.bind(self))
.then(self.render.bind(self))
end

fetch是獲取接口數據,parse解析數據,最終賦值給data。render是模板引擎編譯的函數。每個函數的返回值都約定是promise,便於做流程控制。

很明顯,biglet和Component是異曲同工的,而fetch是對象上的方法,必須實例化biglet才能調用,而next的做法getInitialProps是靜態方法,不必實例化,內存和複用性上都是非常好的。

這點在egg-react-ssr項目技術選型調研期,我們就已經達成一致了。問題是,next只支持SSR,如何能夠更好的支持CSR?做到真正的組件級別的同構。於是,基於這種寫法,通過高階組件進行包裝,輕鬆實現了CSR,核心代碼如下。

image.png

既然寫法上統一了,那麼,我們還能進一步進行優化麼?核心點在網絡獲取部分。我們能看到的gRPC-web或isomorphic-fetch,分別實現了:1)RPC和http的約定,2)CSR和SSR中fetch統一。給我們帶來的啟示是在getInitialProps裡,我們還可以做更多同構的玩法。

image.png

在webpack打包構建server bundle了的時候會注入isBrowser變量。

const plugins = [
  new webpack.DefinePlugin({
    '__isBrowser__': false //eslint-disable-line
  })
]

以此來區分CSR和SSR做同構兼容就更簡單了。

Page.getInitialProps = async (ctx) => {
    if (__isBrowser__) {
        // for CSR
    } else {
        // for SSR
    }
}

以上做法,都是我們基於 https://github.com/ykfe/egg-react-ssr 提煉出來的實踐,這些都是SSR-spec的基礎。

FaaS和SSR如何結合

有了egg-react-ssr,確立了最佳寫法,接下來就是結合FaaS做好集成。

image.png

對比一下,getInitialProps的參數和FaaS函數的參數都有context,這個是非常好的

解法:給 context 擴展SSRRender方法。

為了演示Serverless下渲染層實現原理,下面會進行簡要說明。在Serverless雲函數裡,一般會有server.yml作為配置文件,這裡以lamda為例子。

image.png

通過這個配置,我們可以看出函數app.server對應的http請求路徑是'/',這個配置其實描述的就是路由信息。對應的app.server函數實現如下圖。

image.png

通過提供ctx.SSRRender方法,讀取dist目錄下的Page.server.js完成服務端渲染。

核心要點:

  • SSRRender方法比較容易實現
  • 採用類似Umi SSR的方式,將源碼打包到Page.server.js文件中
  • 在發佈的時候,將配置,app.server函數和Page.server.js等文件上傳到Serverless運行環境即可

架構升級4階段

縱觀SSR相關技術棧的演進過程,我們大致可以推出架構升級的4階段。

  • CSR,很多中後臺都是這樣的開發的,最常見
  • 其次是阿里開源的beidou,基於egg做的React SSR,這是一個集成度很高的項目,很好用,難度也很大
  • Umi SSR是基於egg-react-ssr上演進出來的,在umi之上,用戶不需要關心webpack,但打包後的代碼返回的是stream,這點抽象,雲謙做的非常到位,對於開發者來說,還是需要自建Node web server的。
  • 在Serverless裡,具體怎麼玩是需要我們來創造的。

image.png

對照上圖,說明如下

  1. 在CSR中,開發者需要關心React和Webpack
  2. 在SSR中,開發者需要關心React、Webpack和Egg.js
  3. 在Umi SSR同構中,開發者需要關心React和Egg.js,由於Umi內置了Webpack,開發者基本不需要關注Webpack
  4. 在Serverless時代,基於FaaS的渲染層,開發者需要關心React,不需要關心Webpack和Egg.js

在這4個階段中,依次出現了CSR和SSR,之後在同構實踐中,對開發者要求更高,甚至是全棧。所有這些經驗和最佳實踐的積累,沉澱出了更簡單的開發方式,在Serverless環境下,可以讓前端更加簡單、高效。

組件即函數

對於組件寫法,我們繼續抽象,將佈局也拉出來。

function Page(props) {
  return <div> {props.name} </div>
}

Page.fetch = async (ctx) => {
  return Promise.resolve({
    name: 'Serverless side render'
  })
}

Page.layout = (props) => {
    const { serverData } = props.ctx
    const { injectCss, injectScript } = props.ctx.app.config
    return (
      <html lang='en'>
        <head>
          <meta charSet='utf-8' />
          <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no' />
          <meta name='theme-color' content='#000000' />
          <title>React App</title>
          {
            injectCss && injectCss.map(item => <link rel='stylesheet' href={item} key={item} />)
         }
        </head>
        <body>
          <div id='app'>{ commonNode(props) }</div>
          {
            serverData && <script dangerouslySetInnerHTML={{
              __html: `window.__USE_SSR__=true; window.__INITIAL_DATA__ =${serialize(serverData)}`
            }} />
          }
          <div dangerouslySetInnerHTML={{
            __html: injectScript && injectScript.join('')
          }} />
        </body>
      </html>
    )
}

export default Page

layout目前看只有第一次渲染有用,但做Component抽象是可以考慮的。現在是首次渲染模式,以後不排除遞歸組件樹的方式(結合bigpipe可以更嗨),那時layout還是有用的。

這樣看來,render、fetch、layout是函數,結合Serverless.yml(f.yml)配置,能否將他們放到配置裡呢?

路由由f.yml的配置文件中,所以在f.yml增加render配置擴展,具體如下。

functions:
  home:
    handler: index.handler
    render:
      - Component: src.home.index
      - layout: src.home.layout
      - fetch: src.home.fetch
      - mode: SSR | CSR(默認SSR)
      - injectScript(自己改loyout更好)
          - runtime~Page.js
          - vendor.chunk.js
          - Page.chunk.js
      - injectCSS
          - Page.chunk.css
      - serverBundle: Page.server.js
    events:
      - http:
          path: /
          method:
            - GET
  news:
    handler: index.handler
    render:
      - Component: src.news.index
      - layout: src.news.layout
      - fetch: src.news.fetch
      - mode: SSR | CSR(默認SSR)
    events:
      - http:
          path: /
          method:
            - GET

元彥提了一個非常好的命名:組件即函數。最貼切不過。

將渲染、接口請求、佈局分別拆成獨立函數,放到配置文件裡。如此做法,可以保證函數粒度的職責單一,對於可選項默認值也更友好。比如沒有接口請求就不調用fetch,或者沒有佈局使用默認佈局。

image.png

這裡再拔高一下,CSR和SSR寫法一致了,這種寫法可以和FaaS結合,也就是說寫法上拆成函數後,前端關心的只有函數寫法了,使得面向組件開發更容易。

我們在抽象一下,提煉3重境界。

  • 組件即函數,就是上面將的內容。
  • 頁面即函數,對開發者而言,其實頁面的概念不大,第一次渲染布局,然後entry執行而已,如果拋開構建和配置細節,頁面就是第一個組件,即函數
  • 頁面即服務,是我之前提的概念,每個頁面對應一個Node服務,這樣的好處是避免服務器雪崩,同時可以降低頁面開發複雜度。

對於頁面即服務來說,一個FaaS函數提供http,天然就是獨立服務,在基建側,網關根據流量決定該服務的容器個數,完美的解決了頁面渲染過程的所有問題,也就是我們只需要關注組件寫法,組件寫法又都是函數。所以,組
件即函數,是Serverless渲染層最好的概括。

下面這張圖很好的表達了組件即函數的核心:統一寫法和用法。

image.png

制定規範之前,定位還是先要想明白的。

  • 在FaaS rutime之上,保證可移植性
  • 通過CSR和SSR無縫切換,可以保證首屏渲染效率
  • 由於面向組件和配置做到輕量級開發,可以很好的結合

統一寫法和用法是件約定大於配置的事兒,約定才是最難的,既要保證功能強大,還要寫法簡單,又要有擴展性。顯然,這是比寫代碼更有挑戰的事兒。

SSR規範

統一寫法,上一小結已經講過了。

image.png

接下來就是規範相關的周邊,如下圖。

image.png

構建,擴展,跨平臺,以及目錄都需要約定。

SSR-spec規範主要定義 SSR 特性,組件寫法、目錄結構以及 f.yml 文件擴展的編寫規範。目錄結構待討論,例如新增功能的API與刪除功能的API理論上應該放在一個project當中,此時應該在src目錄下建立不同的文件夾來隔離不同函數的模塊.

├── dist // 構建產物
│   ├── Page.server.js // 服務端頁面bundle
│   ├── asset-manifest.json // 打包資源清單
│   ├── index.html // 頁面承載模版文件,除非想換成傳統的直接扔一個html文件部署的方式
│   └── static // 前端靜態資源目錄
│       ├── css
│       └── js
├── config // 配置
│   ├── webpack.js // webpack配置文件,使用chainWebpackConfig方式導出,非必選
│   └── other // 
├── index.js // 函數入口文件
├── f.yml // FaaS函數規範文件
├── package.json
├── src // 存放前端頁面組件
│   ├── detail // 詳情頁
│   │   ├── fetch.js // 數據預取,非必選
│   │   ├── index.js // React組件,必選
│   │   └── layout.js // 頁面佈局,非必選,沒有默認使用layout/index.js
│   ├── home // 首頁
│   │   ├── fetch.js
│   │   ├── index.js
│   │   └── layout.js
│   └── layout
│       └── index.js // 默認的佈局文件,必選,腳手架默認生成
└── README.md //

命令用法

$ SSR build
$ SSR deploy

生成的dist目錄結構如下。

  • dist

    • funcName

      • static

        • clientBundle.js
        • js
        • css
        • images
      • serverBundle.js

構建命令

$ SSR build
$ SSR build --spa
$ SSR build hello
$ SSR build hello2

Serverless集成步驟,通用方案集成

$ SSR xxx
$ Serverless deploy // f deploy

這裡以Umi為例,開發過程分3個步驟。

  1. 本地源碼組件開發,然後通過umi dev完成構建
  2. 如果是線上,可以走線上構建服務(webpack打包),如果需要修改,可以走
  3. 構建後的產物結合FaaS函數,直接發佈到Serverless平臺上

image.png

規範裡還有很多點也是有思考的,比如多組件支持是基於bigpipe的方式,首先寫入layout佈局,然後處理多個組件的組合邏輯,最終res.end即可。另外,組件上如果只有fetch方法,沒有render方法也是沒有問題的。寫法有2種,Component的值是數組,即串行方式。Component的值是對象,即並行方式。限於篇幅,這裡就不一一贅述了。參見http://gitlab.alibaba-inc.com/Node/SSR-spec

落地實踐、性能和未來思考

打包與構建

Umi的實現是非常巧妙的,核心在於構建後的server bundle返回值是stream,解耦了對web框架的依賴。在當時還沒有實現更好的,所以以Umi為例。

image.png

構建產物,各個文件大小的說明,有2種方式。

  • Node_modules打包到bundle,對FaaS runtime無依賴。
  • Node_modules不打包到bundle,放到FaaS runtime裡。

這2種方式打包大小可以接受,性能上第二種會更好一點,但沒有差很多。

image.png

快速切換CSR還是SSR

前面說了寫法上的統一,在工程實踐中,可以在配置裡,直接設置type快速切換CSR還是SSR。在公司內部,還可以通過diamand配置下發的方式進行動態控制。

image.png

其實SSR-spec規範裡,還做了更多擴展.

//檢查query的信息或者url查詢參數或者頭信息
conf.mode = req.query.SSR || req.headers['x-mode-SSR'];

容災打底方案

簡單說,3層容災

  • Node SSR優先,Node調用hsf。
  • Node CSR通過diamind可以快速切換,html是Node吐出的,前端走MTop請求。
  • 當Node服務掛掉,走CDN上的純CSR,前端MTop請求。

該流程有以下優點:

  • 構建方式一致

    • 服務端/客戶端文件構建方式一致
  • 發佈方式一致

    • 發佈方式一致,統一發布到 CDN,前端資源可以使用 CDN 加速
  • 無需服務端發佈

    • 組件代碼變動統一使用 diamond 下發版本號,無需服務端發佈
  • 及時生效

    • diamond 配置下發後可及時生效

如圖。

image.png

性能優化

性能優化的要點

  • 控制SSR中server端佔的比例,性能和體驗是魚和熊掌不可兼得。看業務訴求。
  • 能緩存的儘量緩存,和BFF應用一樣。

這裡舉個例子,對接口字段瘦身,就可以渠道意想不到的效果。

image.png

性能對比

優酷PC的React SSR性能很好,從v1(Bigpipe+jQuery)到v2(React SSR),性能逐步提升。PC頁面首屏渲染降到150ms、播放器起播時間從4.6秒優化到2秒。H5站上了React SSR後,性能提升3倍,H5喚端率提升也極其明顯,頭條短視頻喚端率由5.68%提升到9.4%,環比提升65%。單機性能qps從80提升150+(壓測最高可以到300左右)。

image.png

淘寶的Rax SSR也性能優異,以一個帶數據請求的真實 Rax SSR 應用為例,性能對比數據顯示:WIFI 下, SSR 的首屏呈現時間相比 CSR 提升 1 倍;弱網環境下,SSR 相比 CSR 提升約 3.5 倍。

參見水瀾Rax SSR:重塑 SSR 應用的開發體驗

SSR Demo 地址:https://Rax-demo.now.sh/SSR/home
CSR Demo 地址:https://Rax-demo.now.sh/CSR/home

未來思考

從用戶訪問頁面流程,具體如下。

image.png

要點

  • 應用網關,肯定是要有的,畢竟要掛域名,反向代理等
  • 頁面,已經可以放到FaaS上,FaaS之上的http網關可以滿足基本需求,離應用級別的還差很多。
  • API代理,這個基於Node FaaS函數非常容易實現
  • API,調用後端服務提供API,比如訪問db等,基於Node FaaS函數也是非常容易實現

FaaS細化到函數粒度,在管理上會有巨大挑戰,在架構上也需要重新設計。

  • 具有前端特色的函數管理:比如API、Page等類型,path,域名,甚至是具體的應用設置
  • 頁面、組件和網關聯動,讓開發更簡單快速
  • 周邊,比如監控,統計,數據等等

這裡嘗試一張圖來表示一下前後端的Serverless時代的分工。

image.png

  • 統一接入網關是必須的,主要是處理頁面和域名的接入。
  • 對於頁面進行抽象,圍繞組件和搭建來展開,通過在線|本地構建,最終放到頁面託管服務中。有了頁面託管,才是萬里長城的一小步。
  • 接下對API進行拆分,這也是組件組成裡重要的部分。

圍繞搭建,可以想象到的是組件和接口的抽象。組件除了智能化我能想到的很少,智能化在計算上也能彈性玩就更有想象力了。對於接口可以再細分

  • 直接操作表,雖不推薦前端做,但確實是必備能力。
  • 通過配置來生成,即元數據管理,對於已有API進行包裝,邏輯編排是非常好的。
  • 如果都不滿足,自己基於FaaS函數定製就好了。

image.png

搭建本身是提效的,組件和接口都能提效,對於前端的價值是尤其大的。前端Serverless專項裡,也是有邏輯編排組的,原因大抵如此。

下面再解釋一下頁面託管服務實現原理。其實這是頁面即服務的升級版,以前每個頁面對應一個Node服務,這就導致很多Node服務的運維成本非常高,有了Serverless,依然還是一個頁面對應一個FaaS函數,但函數的擴容是Serverless基建做的事兒。也就是說頁面託管,其實是基於頁面的FaaS函數的管理。垂直到頁面管理,一切都自動化,會讓開發更簡單。

目前每個FaaS函數是可以提供http透出地址的,但這個地址不具備定製能力,所以在頁面託管服務之上有一層應用網關是必須的。那麼,應用網關和頁面頁面託管服務之間如何聯動呢?

  • 最外層,統一接入網關裡做應用管理,每個應用都有對應的域名和子應用網關,二者進行綁定 根據流浪,子應用網關也可以自動擴縮容。
  • 在應用設置裡,管理子應用網關包含的path和頁面,提供反向代理相關的基礎功能即可。
  • 設置完子應用包含的頁面之後,系統具備將對應頁面同步到子應用網關的能力,並且當頁面更新的時候能夠自動同步,類似於etcd/consul等服務發現同步功能。

有了這部分設計,開發者只需要關注頁面的編寫就好了。比較上面的3點配置在系統中並不經常做。

image.png

基於上面的設計,目的是提高開發速度,沉澱前端中臺,具體好處如下。

  • 中後臺能夠一定程度的收斂到一起,配置化
  • 頁面和系統分離,應變能力更強,結合微前端可以有更多想象力
  • 所有開發聚焦到頁面維度,能夠更好的提效,比如組件沉澱,智能化,搭建等都可以更專注。
  • 很多後端服務也能夠很好的沉澱和複用,這和後端常說的能力地圖類似,將BFF聚合管理,簡化開發

image.png

關於未來,我能想到的是

  • 組件:寫代碼或智能化生成
  • 頁面:配置出來
  • 系統:配置出來

只需要組件級別的函數的輕量級開發模式裡,必然會簡化前端開發方式,提供人效,最終實現技術賦能業務的目的。在“大中臺,小前臺”的背景下,貢獻前端應變能力。

image.png

總結

在《2019,如何放大大前端的業務價值?》一文中,我曾提過:“前端技術趨於成熟,不可否認,這依然是個大前端最好的時代,但對前端來說更重要的是證明自己,不是資源,而是可以創造更多的業務價值。在垂直領域深耕可以讓大家有更多生存空間,但我更願意認為Serverless可以帶來前端研發模式上的顛覆,只有簡化前後端開發難度,才能更好的放大前端的業務價值。”

致敬所有為Serverless付出的同仁們!

image.png

Leave a Reply

Your email address will not be published. Required fields are marked *