億級流量搜索前端,是怎麼做架構升級的?

一、什麼是前端架構?

前端架構這一詞,相信很多人的定義都不太一樣;按照拆詞的解釋來看,我理解爲 “前端”+“架構”。前端是指,Web 端的前臺頁面,包括網頁的內容、樣式、腳本等,這三者通常封裝在組件中,可能是模板引擎的文件模塊,也可能是 MVVM 框架裏的組件。“架構” 就更好理解了,架構一詞來自建築行業,可以理解是房屋的整體結構、框架。結合前端和架構的概念,“前端架構”可以理解爲,Web 頁面組件的抽象和組織方式。

又因爲各個公司的業務不同,每個公司的前端架構發展都不一樣,這裏,我會拿百度移動端經典的搜索場景來給大家舉例,希望從百度的移動端架構演進過程中,發現一些共性的問題。

**
二、百度移動端背景及問題**

爲什麼是以百度來舉例?是因爲百度是國內搜索引擎的領頭人,並且,目前一直處於行業領先狀態。據 statcounter 前瞻產業研究院在 2019 年中國搜索引擎行中可以知道,百度搜索佔全世界搜索引擎市場份額 12.3%,居第二位,僅次於谷歌。所以用百度來舉例,更具有代表性。

言歸正傳,打開百度 App 你會發現,百度前端直接分爲首頁和搜索結果頁,搜索結果頁是搜索的主要入口,每天承載着十億級流量。

不僅如此,搜索結果頁承載着許多產品線的需求和下游模塊的運行時,每年內部的研發人員會提供五百多個產品需求,爲十幾個下游模塊提供基礎庫和運行時。甚至還有後端協同,從圖 1 我們可以看出結果頁的整體架構。

△圖 1:百度搜索結果頁的整體架構

針對整體的架構設計,有這些問題:

於是,梳理出三個方面的問題:

  1. 人員職責不清晰,單個模塊同時承擔了多個團隊的職責
  1. 代碼耦合嚴重
  1. 技術棧落後

這三個問題最終會影響到研發效率以及產品質量。那麼百度又是怎麼去具體做的呢?架構優化的目標只有兩個,一是滿足業務需求,二是技術上能對框架和工具靈活升級(也是爲了持續的滿足業務需求)。根據 “滿足業務需求” 這一目標,百度內部是制定了三個層面的方向。(如圖 2)

△圖 2:業務需求的三個方向

三、怎麼解決

根據這裏提到的方向和目標,怎麼結合百度自己的架構落地呢?首先,回顧下百度的架構,如下圖 3 可以看到。

△__圖 3:百度搜索結果頁的整體架構

  1. 這裏有兩塊日誌,意味着同一套代碼要在兩個部分維護;除了重複之外,它們的差異會對後續的維護引入更高的成本;

  2. 底層這個 HHVM+PHP 和社區更加擁抱 Node.js 會有衝突。

所以,百度同學把目標架構調整爲圖 4 所示。

△圖 4:結果頁的目標架構

圖 4 中可以看到:

  1. 把日誌、搜索框、相關搜索、性能打點等獨立成單獨的模塊,有專門的同學來獨立維護和迭代;

  2. 在前後端之間加了一層渲染層;讓業務代碼和後端的邏輯分開;

  3. 在底層加了 Node.js 機制。

目標、方向都解決好之後,就得看如何實施。對於一個小體量的庫來說,從零構建架構就行;但是對於百度來說,實施也是難點。不僅要考慮平滑遷移、性能不退化,還要考慮長期可維護性、安全性、跨平臺等。

前文也提到了,基本思路是按照基礎設施、模塊拆分、組件化的步驟執行;基礎設施是業務模塊劃分的關鍵,完善的自動化和工具鏈是模塊化的前提;模塊化拆分可以爲業務和團隊提供更好的橫向擴展能力;模塊化的基礎上,可以進一步在模塊內部建設組件化方案來加速業務迭代。

**在基礎設施需要關注的事情包括:
**

**模塊化拆分需要關注的事情包括:
**

**注意:
**

模塊化拆分不是技術問題,而是業務問題。只有根據業務和產品進行垂直劃分,纔有可能達到解耦和獨立迭代的目的。否則只是形式上拆分耦合的代碼,會造成更大的維護和溝通成本。

由於組件是業務模塊內部的選型,組件化的方案相對比較自由。只需要不嚴重影響性能,且能夠平滑過渡即可。

四、落地方案

1. 模塊化

具體的落地方案,我們也用一張圖(圖 5)來表示。可以看到它分爲服務端和瀏覽器端兩部分。

△圖 5:具體的落地方案

**2. 服務端
**

百度是把整個大模塊拆分成多個獨立業務模塊,最終頁面由模塊組合而成。這要求業務模塊具有統一的接口,即上圖所示的 Molecule 接口,它定義了模塊如何渲染、有哪些依賴等信息。因爲渲染過程封裝在了模塊內部,所以整個架構可以支持多語言、多框架。

相信你也發現,Molecule 和微服務非常相似。它們的關鍵區別在於,微服務的服務之間通過 IPC 互相操作,且每個服務可以獨立伸縮、獨立部署;而 Molecule 的各模塊存在於同一個進程裏。雖然有這樣的區別,Molecule 仍然可以實現和微服務近乎相同的特性,如圖 6 所示。

△圖 6:Molecule 和微服務的比較

圖 7 展示的是一個具體的業務模塊的服務端入口文件,其中 ToptipController 是實現了由 Molecule 提供的控制器接口;這個接口要求提供一個渲染函數,接受一個字典類型的數據,返回渲染之後的頁面內容。由調用方決定如何組裝頁面。

△圖 7:具體的業務模塊的服務端入口文件

如上是業務模塊提供方的接口。此外 Molecule 機制還爲調用方(組裝最終頁面的那一側)提供了方便的接口,可以在需要引入子模塊的地方,傳入子模塊名稱和參數即可在運行時渲染出來。整個機制的原理很簡單,但實際使用中可能還需要引入命名空間、考慮模塊版本等問題。

**3. 客戶端
**

那麼客戶端如何運行起來呢?我們也需要把每個模塊的瀏覽器端組件運行起來,困難在於組件之間的依賴和代碼共享。這些組件可能位於不同的代碼庫並屬於不同的業務,所以我們需要一個非常鬆散的依賴方式。

這裏我們引入的是一個依賴注入的容器(圖 8),總的來說,框架邏輯和通用工具都封裝成具體的 Service 提供給業務模塊使用,每個業務模塊則需要定義它依賴於哪些 Service。

△圖 8:客戶端設計

圖 9 形象地描述了組件、Service 和容器間的關係。

△圖 9:組件、Service 和容器之間的關係

其中藍色代表具體的 Service,其他顏色表示獨立的業務模塊。運行時容器會負責解決每個業務模塊的依賴,並把這些業務模塊組裝起來,最終得到可交互的 Web 頁面。

注意:

業務模塊之間是獨立的,一個業務模塊無法依賴於其他業務模塊,只能依賴於通用 Service。因此如果存在業務模塊之間的產品邏輯耦合,可能需要一個通用 Service 作爲媒介,比如容器裏提供一個起事件總線作用的 EventService。

圖 10 是業務模塊的客戶端代碼示例。它的依賴通過構造函數來聲明,運行時容器負責依賴的創建,而業務模塊只需要關心依賴的使用。正是使用和創建操作的分離,使得業務模塊之間、業務模塊和頁面框架之間可以解耦,可以獨立地開發、獨立地測試。

△圖 10:業務模塊的客戶端代碼示例

以上是模塊拆分的整體方案,我們回顧一下:在服務端通過一個叫做 Molecule 的接口來組合業務模塊;在瀏覽器端通過一個 DI 容器來解決依賴關係,並啓動所有業務模塊。

4. 組件化

組件化方案直接影響業務開發的的效率,換句話說,組件化方案某種程度上決定了業務同學寫怎樣的代碼。組件化也可以幫助解決職責不清晰等問題。我們選的組件化方案是 San,你也可以基於你的業務或偏好選則 Vue 或者 React。業務代碼的遷移比較直觀,就是從 Smarty 模板遷移到 San 組件,從 HTML 字符串拼接變成有業務語義的組件結構。

接下來重點關注組件化方案的兩個關鍵技術問題,跨平臺和頁面性能。

1)跨平臺

我們有非常多的業務代碼,有上千個模板、幾十萬行代碼,這些代碼需要遷移到組件化方案上來,而且要確保後端從 PHP 遷移到 Node.js 的整個過程中,業務代碼不需要重新開發。所以業務組件如何跨平臺呢?關鍵在於抽象。

做到以上幾點就可以完成平滑的過渡。這個過程中又分爲三個階段(圖 11)。

△圖 11:平臺過渡的三個階段

2)頁面性能

引入前端框架通常意味着體積增加,性能下降,而性能直接影響搜索收入,因此頁面性能是項目成功的關鍵。如果性能會比模板引擎的性能差,那麼這個項目很可能會夭折。如何去保證頁面性能?着重介紹兩個優化點。

**引入 SSR。**爲了解釋 SSR 的重要性,請看圖 12。瀏覽器加載頁面分爲四步:請求頁面、請求外鏈資源、執行腳本、渲染組件。從圖中的對比可以看出,CSR 在前面三步的時候,用戶都是看不到頁面的;而引入 SSR 之後,在第二步用戶就能看到請求回來的頁面。SSR 它最大的一個用途就是提升首屏時間。

△圖 12:CSR 和 SSR 的比較

**SSR 優化。**只是引入 SSR 還不能讓性能達到預期,因爲相比於模板引擎直接拼接字符串,SSR 需要遞歸渲染組件,尤其是遞歸 VNode 比較耗時。對此 San SSR 相比於 Vue/React SSR 做了很多改進。

圖 13 展示了最終的 San SSR 和改造前的 Smarty 模板引擎的性能對比。

△圖 1__3:最終的 San SSR 和改造前的 Smarty 模板引擎的性能對比

可以看到 Smarty 和 San SSR 在不同的場景會有不同的表現,因爲它們的渲染方式非常不同。最終搜索結果頁的組件化的 SSR 上線之後,線上實驗效果顯示比 Smarty 要快 10ms 左右。這個已經是一個很不錯的效果了,我們用組件化從性能上打敗了模版引擎。

五、結語

針對百度搜索引擎在架構演化中遇到的問題,相信在其他領域也會有一些共性的東西。通過百度的解決思路,希望能對正在做前端架構的你有一些啓發。

Harttle

百度資深研發工程師,北京大學物理學學士和計算機科學碩士。2016 年加入百度,曾負責和參與百度搜索 Web 極速瀏覽框架、MIP 開源項目的研發,目前負責搜索結果頁和搜索推薦業務。LiquidJS 的作者,貢獻於 San、Realworld Apps、hightlight.js、ALE、HTML5 Standard 等項目。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/67aRE2Zfh8FEN8T0_ouaGQ