導讀:前端發展飛速,從最開始的靜態頁面到 JavaScript,再從 PC 端到移動端,隨着大前端的複雜度不斷提高,不少公司開始先後端分離,剝離出前、後端架構設計。那咱們來看看,前端架構設計是什麼?曾經很是簡單的前端架構發展到如今有哪些問題,遇到前端代碼體量巨大、跨團隊協做效率、代碼耦合、技術棧落後等問題又該怎麼解決?
前端
前端架構這一詞,相信不少人的定義都不太同樣;按照拆詞的解釋來看,我理解爲「前端」+「架構」。前端是指,Web 端的前臺頁面,包括網頁的內容、樣式、腳本等,這三者一般封裝在組件中,多是模板引擎的文件模塊,也多是 MVVM 框架裏的組件。「架構」就更好理解了,架構一詞來自建築行業,能夠理解是房屋的總體結構、框架。結合前端和架構的概念,「前端架構」能夠理解爲,Web 頁面組件的抽象和組織方式。
後端
又由於各個公司的業務不一樣,每一個公司的前端架構發展都不同,這裏,我會拿百度移動端經典的搜索場景來給你們舉例,但願從百度的移動端架構演進過程當中,發現一些共性的問題。瀏覽器
言歸正傳,打開百度 App 你會發現,百度前端直接分爲首頁和搜索結果頁,搜索結果頁是搜索的主要入口,天天承載着十億級流量。
安全
不只如此,搜索結果頁承載着許多產品線的需求和下游模塊的運行時,每一年內部的研發人員會提供五百多個產品需求,爲十幾個下游模塊提供基礎庫和運行時。甚至還有後端協同,從圖 1 咱們能夠看出結果頁的總體架構。前端框架
△圖 1:百度搜索結果頁的總體架構架構
針對總體的架構設計,有這些問題:app
細分業務線衆多,單個庫代碼龐大;框架
平均每個月有 200+ 提交,3w+ 行代碼;前後端分離
80+ 開發者在同一個代碼庫中開發;ide
沒有人能徹底掌握模塊總體技術。
因而,梳理出三個方面的問題:
1. 人員職責不清晰,單個模塊同時承擔了多個團隊的職責
框和 Tab:「所有」和垂類搜索共用;
運營產品:***在結果頁代碼庫裏;
其餘:結果列表、用戶反饋、搜索推薦、體驗日誌、速度日誌、計費邏輯……
2. 代碼耦合嚴重
容易出錯,代碼邏輯脆弱;
結構僵化,不易新增功能;
依賴牢固,代碼很難複用。
3. 技術棧落後
頁面沒有組件化。沒有 Vue、沒有 React,還在用 Smarty 模板;
沒法支持 Node.js。Smarty 模板強依賴 PHP 環境;
工具鏈落後。沒有 TypeScript、沒有 Jest。
這三個問題最終會影響到研發效率以及產品質量。那麼百度又是怎麼去具體作的呢?架構優化的目標只有兩個,一是知足業務需求,二是技術上能對框架和工具靈活升級(也是爲了持續的知足業務需求)。根據「知足業務需求」這一目標,百度內部是制定了三個層面的方向。(如圖 2)
底層基礎層是貼近社區,由於據內部調研來看,造輪子的成本不高,可是維護這些輪子成本極高,若是想更快的迭代,仍是建議貼近社區,去用些開源的事情或者去貢獻開源。主要是解決技術棧落後以及職責不清晰等問題。
中間層是獨立模塊,主要是應對以前提到的職責不清晰的問題以及交付效率低等問題。主要是解決職責不清晰以及交付效率低等問題。
頂層就是組件化,在獨立模塊的基礎上去作組件化,加速業務的迭代。
△圖 2:業務需求的三個方向
根據這裏提到的方向和目標,怎麼結合百度本身的架構落地呢?首先,回顧下百度的架構,以下圖 3 能夠看到。
△圖 3:百度搜索結果頁的總體架構
1. 這裏有兩塊日誌,意味着同一套代碼要在兩個部分維護;除了重複以外,它們的差別會對後續的維護引入更高的成本;
2. 底層這個 HHVM+PHP 和社區更加擁抱 Node.js 會有衝突。
因此,百度同窗把目標架構調整爲圖 4 所示。
△圖 4:結果頁的目標架構
圖 4 中能夠看到:
把日誌、搜索框、相關搜索、性能打點等獨立成單獨的模塊,有專門的同窗來獨立維護和迭代;
在先後端之間加了一層渲染層;讓業務代碼和後端的邏輯分開;
在底層加了 Node.js 機制。
目標、方向都解決好以後,就得看如何實施。對於一個小體量的庫來講,從零構建架構就行;可是對於百度來講,實施也是難點。不只要考慮平滑遷移、性能不退化,還要考慮長期可維護性、安全性、跨平臺等。
前文也提到了,基本思路是按照基礎設施、模塊拆分、組件化的步驟執行;基礎設施是業務模塊劃分的關鍵,完善的自動化和工具鏈是模塊化的前提;模塊化拆分能夠爲業務和團隊提供更好的橫向擴展能力;模塊化的基礎上,能夠進一步在模塊內部建設組件化方案來加速業務迭代。
在基礎設施須要關注的事情包括:
TypeScript:大型項目必備,提早發現問題;也是跨平臺的基礎;
持續集成:確保每次變動新增功能和修復問題的同時,不引入新的問題;
單元測試:在重構之初引入,幫助防退化和輔助設計。
模塊化拆分須要關注的事情包括:
識別和定義業務邊界,把大一統的倉庫分割成若干獨立的小倉庫;
在子模塊內建設自動化機制,獨立地選型、開發、上線。
注意:
模塊化拆分不是技術問題,而是業務問題。只有根據業務和產品進行垂直劃分,纔有可能達到解耦和獨立迭代的目的。不然只是形式上拆分耦合的代碼,會形成更大的維護和溝通成本。
因爲組件是業務模塊內部的選型,組件化的方案相對比較自由。只須要不嚴重影響性能,且可以平滑過渡便可。
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 的整個過程當中,業務代碼不須要從新開發。因此業務組件如何跨平臺呢?關鍵在於抽象。
高層語言:咱們業務代碼須要使用一個足夠高層的語言,這裏咱們用的是 TypeScript,能夠翻譯到多個平臺;
依賴反轉:咱們的高層的業務的模塊不該該依賴於具體的底層模塊,而是它只依賴於接口,這樣纔有可能在不一樣的平臺給它替換掉不一樣的底層的實現;
抽象接口:最後是 Molecule 這個接口的設計應該足夠的簡單;Molecule 接口不依賴底層實現,好比 PHP 的具體 API。
作到以上幾點就能夠完成平滑的過渡。這個過程當中又分爲三個階段(圖 11)。
△圖 11:平臺過渡的三個階段
2)頁面性能
引入前端框架一般意味着體積增長,性能降低,而性能直接影響搜索收入,所以頁面性能是項目成功的關鍵。若是性能會比模板引擎的性能差,那麼這個項目極可能會夭折。如何去保證頁面性能?着重介紹兩個優化點。
引入 ***:引入服務端渲染,首屏性能能夠獲得明顯提高;
*** 優化:傳統的 *** 上還須要進一步優化性能。
引入***。爲了解釋***的重要性,請看圖12。瀏覽器加載頁面分爲四步:請求頁面、請求外鏈資源、執行腳本、渲染組件。從圖中的對比能夠看出,CSR在前面三步的時候,用戶都是看不到頁面的;而引入***以後,在第二步用戶就能看到請求回來的頁面。***它最大的一個用途就是提高首屏時間。
△圖 12:CSR和***的比較
*** 優化。只是引入 *** 還不能讓性能達到預期,由於相比於模板引擎直接拼接字符串,*** 須要遞歸渲染組件,尤爲是遞歸 VNode 比較耗時。對此 San *** 相比於 Vue/React *** 作了不少改進。
去 VNode:編譯期遞歸 VNode,運行時只作 HTML 拼接;
編譯期計算:儘量把工做移到編譯期,減少運行時開銷;
圖 13 展現了最終的 San *** 和改造前的 Smarty 模板引擎的性能對比。
△圖 13:最終的 San *** 和改造前的 Smarty 模板引擎的性能對比
能夠看到 Smarty 和 San *** 在不一樣的場景會有不一樣的表現,由於它們的渲染方式很是不一樣。最終搜索結果頁的組件化的 *** 上線以後,線上實驗效果顯示比 Smarty 要快 10ms左右。這個已是一個很不錯的效果了,咱們用組件化從性能上戰勝了模版引擎。
5、結語
針對百度搜索引擎在架構演化中遇到的問題,相信在其餘領域也會有一些共性的東西。經過百度的解決思路,但願能對正在作前端架構的你有一些啓發。
Harttle
百度資深研發工程師,北京大學物理學學士和計算機科學碩士。2016年加入百度,曾負責和參與百度搜索Web極速瀏覽框架、MIP開源項目的研發,目前負責搜索結果頁和搜索推薦業務。LiquidJS 的做者,貢獻於San、Realworld Apps、hightlight.js、ALE、HTML5 Standard等項目。
閱讀原文:十億級流量的搜索前端,是怎麼作架構升級的
更多幹貨、內推福利,歡迎關注同名公衆號「百度Geek說」~