
在今天,對于Node.js運維和高并發依然是很有挑戰的,為了提效,将架構演進為頁面即服務,可是粒度還不夠,借着雲原生和Serverless大潮,無運維,輕松擴充,對前端是極大的誘惑。那麼,基于FaaS之上,前端有哪些可能性呢?
2019年上半年,我在阿裡巴巴經濟體前端委員會推進的Serverless研發體系共建項目中負責Serverless SSR的研究,将CSR,SSR,邊緣渲染進行整合和嘗試,提出元件即服務的概念(Component as Service),試圖結合FaaS,做出更簡單的開發方式。本次分享主要圍繞Serverless SSR和它的演進過程、背後思考為主。
本文是狼叔在D2大會的分享《前端新思路:元件即服務和Serverless SSR實踐》的内容,閱讀需要10分鐘,你将了解如下内容。
- 可以了解Serverless時代端側渲染面臨的具體問題
- 可以了解Serverless SSR規範以及渲染體系的完整工作鍊路和原理
- 為業内提供解決Serverless SSR渲染問題的新思路
狼叔(網名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.
技術趨勢分析
今年的技術趨勢,我的判斷是技術混亂期已過,提效才是今日的挑戰。
在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 重構之路,Serverless SSR 未來可期》- 将優酷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點。
- 寫法上的統一:CSR和SSR可以共存,繼而實作二種模式的無縫切換
- 容災降級方案:從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的工作。那麼,差異在哪裡呢?
核心是從Node到FaaS,本質上還是Serverless,省的其實隻是運維和自動擴縮容的工作,一切看起來都是基建的功勞,但對于前端來說,卻是極為重大的痛點解決方案,能夠滿足所有應用場景,基于函數粒度可以簡化開發,乃生死必争之地。
SFF前後端分工
Serverless簡單了解是FaaS+BaaS。FaaS是函數即服務,應用層面的對外接口,而BaaS則是後端即服務,更多的是業務系統相關的服務。當下的FaaS還是圍繞API相關的工作為主,那麼,前端如何和Serverless綁定呢?
Serverless For Frontend(簡稱SFF)便是這樣的概念,基于Serverless架構提供對前端開發提效的方案。
下面看一下SFF分工,這張圖我自認為還是非常經典的。首先将Serverless劈成2半,前端和後端,後端的FaaS大家都比較熟悉了,但前端頁面和FaaS如何內建還是一片待開發的新領域。
舉個例子,常見BFF的例子,hsf調用獲得服務端資料,前端通過ctx完成對前端的輸出。這時有2種常見應用場景
- API,同後端FaaS(RPC居多)
- 頁面渲染(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)可擴充
目前還是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為例子。
SSR概念更新
現狀
- 現有FaaS主要是針對API層做擴充,視圖渲染是一個新的命題。
- 競品Serverless.com提供了Components類似的視圖渲染層方案
- 如何打造一個基于阿裡技術棧又有業界領先的端側渲染解決方案
業界還缺少最佳實踐,這是極好的機會。是以,我們對SSR做了概念上的更新(感謝justjavac大佬的提示)
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個術語。
CSR和SSR
先科普一下CSR和SSR的概念
用戶端渲染(簡稱CSR),簡單了解就是html是沒有被動态資料灌入的,即所謂的靜态頁面。比如通過React、Rax編寫的元件,打包建構後,以html檔案形式分發到CDN上,不需要Node.js支援,就是非常典型的CSR。
資源加載完成後(注意:隻有一個bundle,一次性吐出),通過React中的render API進行頁面渲染。優點是不需要服務端接入,簡單,對于性能要求不高的頁面是非常合适的。中背景應用大多是CSR,優化也都是打包環節玩。
伺服器端渲染(SSR),簡單了解就是html是由服務端寫出,可以動态改變頁面内容,即所謂的動态頁面。早年的php、asp、jsp這些Server page都是SSR的。但基于React技術棧,又有些許不同,server bundle建構的 時候,要吐多少子產品,是server端決定的。client bundle和之前一樣,差别在于這次是hydrate,而非render。
hydrate是 React 中提供在初次渲染的時候,去複用原本已經存在的 DOM 節點,減少重新生成節點以及删除原本 DOM 節點的開銷,來加速初次渲染的功能。主要使用場景是服務端渲染或者像prerender等情況,是以在圖中hydrate之後才是tti時間。
如果想全局了解CSR和SSR,共分5個階段,參考下圖。
純服務端渲染和純用戶端渲染是2個極端,React SSR是屬于中間的,這種服務端吐出的粒度是可以根據業務來控制的,可以服務端多一點,性能會差,也可以服務端吐出剛好夠首屏的資料,其他由用戶端來處理,這種性能會好很多。Static SSR和預渲染的CSR也是特定場景優化的神器。
最佳寫法
了解了CSR和SSR的差別,下面我們看一下最佳寫法是如何演進的。業内最好的實作大概是next.js了,抛開負責度不談,單就寫法來說,它确實是最合理的。
既然是基于React做法,核心肯定以Component為主,在Component上擴充靜态方法用于接口請求是很好的實踐。寫法如下。
早年寫過bigpipe相關事項,其中子產品成為biglet,它的作用是擷取接口,結合tpl生成html。
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,核心代碼如下。
既然寫法上統一了,那麼,我們還能進一步進行優化麼?核心點在網絡擷取部分。我們能看到的gRPC-web或isomorphic-fetch,分别實作了:1)RPC和http的約定,2)CSR和SSR中fetch統一。給我們帶來的啟示是在getInitialProps裡,我們還可以做更多同構的玩法。
在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做好內建。
對比一下,getInitialProps的參數和FaaS函數的參數都有context,這個是非常好的
- egg-react-ssr中getInitialProps參數ctx是egg/koa的ctx
- FaaS函數的參數context挂了各種函數、記憶體、日志、client相關的資訊,把response資訊挂上理論上也是可行的,隻是處理位置問題,參見 https://docs.aws.amazon.com/lambda/latest/dg/Nodejs-context.html
解法:給 context 擴充SSRRender方法。
為了示範Serverless下渲染層實作原理,下面會進行簡要說明。在Serverless雲函數裡,一般會有server.yml作為配置檔案,這裡以lamda為例子。
通過這個配置,我們可以看出函數app.server對應的http請求路徑是'/',這個配置其實描述的就是路由資訊。對應的app.server函數實作如下圖。
通過提供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裡,具體怎麼玩是需要我們來創造的。
對照上圖,說明如下
- 在CSR中,開發者需要關心React和Webpack
- 在SSR中,開發者需要關心React、Webpack和Egg.js
- 在Umi SSR同構中,開發者需要關心React和Egg.js,由于Umi内置了Webpack,開發者基本不需要關注Webpack
- 在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,或者沒有布局使用預設布局。
這裡再拔高一下,CSR和SSR寫法一緻了,這種寫法可以和FaaS結合,也就是說寫法上拆成函數後,前端關心的隻有函數寫法了,使得面向元件開發更容易。
我們在抽象一下,提煉3重境界。
- 元件即函數,就是上面将的内容。
- 頁面即函數,對開發者而言,其實頁面的概念不大,第一次渲染布局,然後entry執行而已,如果抛開建構和配置細節,頁面就是第一個元件,即函數
- 頁面即服務,是我之前提的概念,每個頁面對應一個Node服務,這樣的好處是避免伺服器雪崩,同時可以降低頁面開發複雜度。
對于頁面即服務來說,一個FaaS函數提供http,天然就是獨立服務,在基建側,網關根據流量決定該服務的容器個數,完美的解決了頁面渲染過程的所有問題,也就是我們隻需要關注元件寫法,元件寫法又都是函數。是以,組
件即函數,是Serverless渲染層最好的概括。
下面這張圖很好的表達了元件即函數的核心:統一寫法和用法。
制定規範之前,定位還是先要想明白的。
- 在FaaS rutime之上,保證可移植性
- 通過CSR和SSR無縫切換,可以保證首屏渲染效率
- 由于面向元件和配置做到輕量級開發,可以很好的結合
統一寫法和用法是件約定大于配置的事兒,約定才是最難的,既要保證功能強大,還要寫法簡單,又要有擴充性。顯然,這是比寫代碼更有挑戰的事兒。
SSR規範
統一寫法,上一小結已經講過了。
接下來就是規範相關的周邊,如下圖。
建構,擴充,跨平台,以及目錄都需要約定。
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
- static
- funcName
建構指令
$ SSR build
$ SSR build --spa
$ SSR build hello
$ SSR build hello2
Serverless內建步驟,通用方案內建
$ SSR xxx
$ Serverless deploy // f deploy
這裡以Umi為例,開發過程分3個步驟。
- 本地源碼元件開發,然後通過umi dev完成建構
- 如果是線上,可以走線上建構服務(webpack打包),如果需要修改,可以走
- 建構後的産物結合FaaS函數,直接釋出到Serverless平台上
規範裡還有很多點也是有思考的,比如多元件支援是基于bigpipe的方式,首先寫入layout布局,然後處理多個元件的組合邏輯,最終res.end即可。另外,元件上如果隻有fetch方法,沒有render方法也是沒有問題的。寫法有2種,Component的值是數組,即串行方式。Component的值是對象,即并行方式。限于篇幅,這裡就不一一贅述了。參見
http://gitlab.alibaba-inc.com/Node/SSR-spec。
落地實踐、性能和未來思考
打包與建構
Umi的實作是非常巧妙的,核心在于建構後的server bundle傳回值是stream,解耦了對web架構的依賴。在當時還沒有實作更好的,是以以Umi為例。
建構産物,各個檔案大小的說明,有2種方式。
- Node_modules打包到bundle,對FaaS runtime無依賴。
- Node_modules不打包到bundle,放到FaaS runtime裡。
這2種方式打包大小可以接受,性能上第二種會更好一點,但沒有差很多。
快速切換CSR還是SSR
前面說了寫法上的統一,在工程實踐中,可以在配置裡,直接設定type快速切換CSR還是SSR。在公司内部,還可以通過diamand配置下發的方式進行動态控制。
其實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 配置下發後可及時生效
如圖。
性能優化
性能優化的要點
- 控制SSR中server端占的比例,性能和體驗是魚和熊掌不可兼得。看業務訴求。
- 能緩存的盡量緩存,和BFF應用一樣。
這裡舉個例子,對接口字段瘦身,就可以管道意想不到的效果。
性能對比
優酷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左右)。
淘寶的Rax SSR也性能優異,以一個帶資料請求的真實 Rax SSR 應用為例,性能對比資料顯示:WIFI 下, SSR 的首屏呈現時間相比 CSR 提升 1 倍;弱網環境下,SSR 相比 CSR 提升約 3.5 倍。
參見水瀾Rax SSR:
重塑 SSR 應用的開發體驗SSR Demo 位址:
https://Rax-demo.now.sh/SSR/homeCSR Demo 位址:
https://Rax-demo.now.sh/CSR/home未來思考
從使用者通路頁面流程,具體如下。
要點
- 應用網關,肯定是要有的,畢竟要挂域名,反向代理等
- 頁面,已經可以放到FaaS上,FaaS之上的http網關可以滿足基本需求,離應用級别的還差很多。
- API代理,這個基于Node FaaS函數非常容易實作
- API,調用後端服務提供API,比如通路db等,基于Node FaaS函數也是非常容易實作
FaaS細化到函數粒度,在管理上會有巨大挑戰,在架構上也需要重新設計。
- 具有前端特色的函數管理:比如API、Page等類型,path,域名,甚至是具體的應用設定
- 頁面、元件和網關關聯,讓開發更簡單快速
- 周邊,比如監控,統計,資料等等
這裡嘗試一張圖來表示一下前後端的Serverless時代的分工。
- 統一接入網關是必須的,主要是處理頁面和域名的接入。
- 對于頁面進行抽象,圍繞元件和搭建來展開,通過線上|本地建構,最終放到頁面托管服務中。有了頁面托管,才是萬裡長城的一小步。
- 接下對API進行拆分,這也是元件組成裡重要的部分。
圍繞搭建,可以想象到的是元件和接口的抽象。元件除了智能化我能想到的很少,智能化在計算上也能彈性玩就更有想象力了。對于接口可以再細分
- 直接操作表,雖不推薦前端做,但确實是必備能力。
- 通過配置來生成,即中繼資料管理,對于已有API進行包裝,邏輯編排是非常好的。
- 如果都不滿足,自己基于FaaS函數定制就好了。
搭建本身是提效的,元件和接口都能提效,對于前端的價值是尤其大的。前端Serverless專項裡,也是有邏輯編排組的,原因大抵如此。
下面再解釋一下頁面托管服務實作原理。其實這是頁面即服務的更新版,以前每個頁面對應一個Node服務,這就導緻很多Node服務的運維成本非常高,有了Serverless,依然還是一個頁面對應一個FaaS函數,但函數的擴容是Serverless基建做的事兒。也就是說頁面托管,其實是基于頁面的FaaS函數的管理。垂直到頁面管理,一切都自動化,會讓開發更簡單。
目前每個FaaS函數是可以提供http透出位址的,但這個位址不具備定制能力,是以在頁面托管服務之上有一層應用網關是必須的。那麼,應用網關和頁面頁面托管服務之間如何關聯呢?
- 最外層,統一接入網關裡做應用管理,每個應用都有對應的域名和子應用網關,二者進行綁定 根據流浪,子應用網關也可以自動擴縮容。
- 在應用設定裡,管理子應用網關包含的path和頁面,提供反向代理相關的基礎功能即可。
- 設定完子應用包含的頁面之後,系統具備将對應頁面同步到子應用網關的能力,并且當頁面更新的時候能夠自動同步,類似于etcd/consul等服務發現同步功能。
有了這部分設計,開發者隻需要關注頁面的編寫就好了。比較上面的3點配置在系統中并不經常做。
基于上面的設計,目的是提高開發速度,沉澱前端中台,具體好處如下。
- 中背景能夠一定程度的收斂到一起,配置化
- 頁面和系統分離,應變能力更強,結合微前端可以有更多想象力
- 所有開發聚焦到頁面次元,能夠更好的提效,比如元件沉澱,智能化,搭建等都可以更專注。
- 很多後端服務也能夠很好的沉澱和複用,這和後端常說的能力地圖類似,将BFF聚合管理,簡化開發
關于未來,我能想到的是
- 元件:寫代碼或智能化生成
- 頁面:配置出來
- 系統:配置出來
隻需要元件級别的函數的輕量級開發模式裡,必然會簡化前端開發方式,提供人效,最終實作技術賦能業務的目的。在“大中台,小前台”的背景下,貢獻前端應變能力。
總結
在《2019,如何放大大前端的業務價值?》一文中,我曾提過:“前端技術趨于成熟,不可否認,這依然是個大前端最好的時代,但對前端來說更重要的是證明自己,不是資源,而是可以創造更多的業務價值。在垂直領域深耕可以讓大家有更多生存空間,但我更願意認為Serverless可以帶來前端研發模式上的颠覆,隻有簡化前後端開發難度,才能更好的放大前端的業務價值。”
緻敬所有為Serverless付出的同仁們!