前端微服務在字節跳動的打磨與應用

本文討論了微前端在字節跳動的應用狀況,內容主要分析了微前端具體落地的步驟和兩年來的使用狀況。其中分析的部分主要講到一些實際問題和咱們的應對,落地狀況強調了實現的過程。特別講到不少在咱們觀念裏面務必要提供的微前端基石,這些方面做爲基礎設施幾乎是使用微前端的必要和前提條件。html

傳統前端業務一般會根據業務線集成在一個站點上,隨着業務複雜度上升,包體積會迅速變的過大。爲了適應這個變化每每須要更多的開發者、更細粒度的團隊組織。分組開發時你們的模塊解耦到各自完成,上線時糅合在一塊兒運行,產生出層出不窮的分支合併、代碼回滾,都會形成合做效率的驟降。這正是頭條號平臺在 17 年時面臨的問題。前端

過大的代碼集合還會形成發佈頻繁,每一個業務分支和功能點都有必定的更新頻率,若是以傳統的獨石系統開發、驗證和上線,每個業務都會讓項目全部一塊兒升級、測試和上線,發佈頻率的總和會很是高、很是頻繁。若是不解除原有的耦合會完全失去響應能力。react

更進一步來看以如此之高的上線頻率、版本迭代速度,開發者極難追溯哪一個版本對應哪一個改動。linux

字節跳動微服務前端解決方案爲應對以上挑戰而生。通過幾年發展已經成功支持了幾十個對內和對外的系統。webpack

問題背景

Monolithic 的問題

Monolith 獨石就是一塊石頭的意思。正常翻譯通常是「單體」:單體應用。這個在前端屆概念不普及,用獨石這個翻譯更能體現他是什麼意思。一整個建築(或者什麼其餘東西)是一整塊石頭刻出來的。好比石獅子。這就是獨石的應用。這樣作事情在前端工程環境這個快速變化、快速迭代的領域有不少問題。git

上線慢

單體應用的一大問題是發佈很是慢。字節跳動的典型業務狀況是上一次線須要至少 30 分鐘,前端的上線就須要這麼長的時間。固然這是咱們在 17 年經歷的狀況,保持咱們的發展態勢若是不升級技術,如今可能更慢。而後 17 年末咱們開始了大改版,開始拼命的擁抱微前端。程序員

本來回滾一次也是 10 分鐘的。因此當時天天上線不了幾回,風險也很大。逐漸致使變動都要憋着,成了「幾天上線一次、一次多個變動」。github

我相信這也是絕大多數聽衆都會有的問題。尤爲是那種傳統的後臺工程。沒事 webpack 一下你懂的。web

上下線會不少嗎?不少的,業務多了,有多少更新都要一塊兒發佈。docker

理解困難

固然本次是工程化的議題。更須要關注的影響更大的實際上是框架問題。你們都是幾十個項目合做到一個工程裏。工程化在搞什麼呢其中很是重要一個點就是要「人能夠理解」。更低的認知成本,能收穫更低的犯錯機率。

那這些項目非得維持徹底一致的組織模型就基本上是必須的。好比 model 是充血的仍是失血的?是 contorller 全都放一塊兒、仍是根據 router 與視圖們放一塊兒?這些事很是雞毛蒜皮的例子。實際上深層次的問題與之相似的很是多。

還有其它問題好比 debug 的時候到底能不能找到。也不是單體應用不行,單純是說解決這個問題的時候投入了多少精力、多少設計,以及維持這個設計規範問題不崩壞,須要多少精力。

這一類都是單體應用自己的代碼問題。「拆了就沒這些事了。」

框架沒法調整

真的從架構角度來講,到底現在的前端項目須要怎麼開發、通常是怎麼幹的呢。從上一段內容讀過來,咱們知道大部分出色的架構師工程師都已經解決了好多那些困難了,方式是經過傑出的架構設計。

而後都知道前端的各類框架各類實踐實際上很是多。前端工程師有個別名不知道大家聽過嗎,叫「npm install 工程師」哈哈,還有「github search 工程師」。

到底發生了極端困難的狀況是來自框架仍是來自生產框架的方法呢,這個很差說。可是是個值得琢磨的問題。因此你看接手拿到項目什麼的別說了你就學吧。反正現有架構確定是挺好的。就是你得學一陣、用對了纔好。

微前端在字節跳動

這裏開始講咱們的細節,分別是服務發現、運行隔離、環境一致以及其餘架構優點,其實這幾件事都講完就能發如今講的主要意思是,具體是什麼把一個很是特殊、對習慣改變比較大的方案變成可能的。實現這樣一個不同凡響的方案不是你們聊一聊以爲贊成而且開心就能作成。涉及到過程、成本、風險等方方面面。

「工程師」的任務不是說證實一個結構在理論上是能夠存在的就完了,要有建造這個結構的過程。好比你拿化學鍵能夠算出來任何可能存在的分子,畫個小人均可以。可是到底怎麼合成,按照什麼路徑能讓這種分子被製造,哪一種路徑最快最便宜,這個是過程的可能性。一般這纔是工程師的任務。

服務發現

服務發現的方面咱們會首先講一下在整個微服務模式裏他做用是什麼、有哪些方式。而後第二部分講到底在解決什麼問題,以及多說一些他能提供的新能力。咱們很重視新能力由於咱們的定位不是消防隊,滅了火就完成任務,還有不少新目標、不少新好處能夠探索實現。

最後是講一下在字節跳動具體是怎麼實現的。

1. 原理

「服務發現」就是原來的單體服務拆分以後,原本一個項目裏的方法分開部署了,誰也找不到誰。須要有個統一的註冊機構,把提供服務的各個部署都查到。

「發現」就是當你想訪問一個微服務,你要怎麼找到他。

這樣就有兩種構型,一個是以 Netflix OSS 爲典型的,它在客戶端的機器裏先拿到一個服務目錄,處理邏輯在客戶端的代碼裏。另外一種是服務端的服務發現,AWS 就是如此。

傳統微服務的服務發現更像是函數調用的替代,拆了以後怎麼調到不一樣容器裏部署的函數。這裏微前端思路很是相似,做用略有區別。微服務的狀況是會區分像什麼訂閱、通知、請求、發佈這些,前端極可能都不用或者表現上不在前端運行時使用。還有一些例如「對單」、「對多」這些基本就是前端不太用考慮的東西。

二者一致的地方是都誰也不認識誰了,如何知道哪些服務存在、誰在提供?下面就要講一下各類構型的服務發現和背後的服務註冊分別具體是什麼狀況。

客戶端服務發現 是說客戶端——也就是服務的調用者,去請求一個註冊的目錄,裏面包含全部服務和負載均衡的基本信息這些,而後本身決定如何處理,使用哪一種具體的 load balance 策略。好比 Netflix 的 OSS,服務在 Netflix Eureka 註冊一下,它心跳給各個客戶端。客戶端本身搞,簡單直觀。

服務端服務發現 是相似 AWS Elastic LoadBalancer 這種。客戶端請求就完了,服務端決定怎麼給你反向代理、負載均衡。

服務註冊 分自注冊和第三方註冊。自注冊不言而喻。第三方註冊就是一個保活機制,按期檢查服務狀態,幫你去管控該上了仍是下了。

咱們主要用的是第一種:客戶端服務發現,就是你要多請求一個模塊列表。這個列表給出的資源是根據用戶 session 決定的,有豐富的動態的能力。而後客戶端再根據這個列表裏的各類信息,去加載模塊資源。

2. 給前端帶來了什麼?

用服務發現的方式去組織微前端,除了使複雜的上線流程變得解耦、快捷,還可使拆散以後的工程版本方便對齊,實現更高的穩定性和可調試性。還對前端工程帶來好多其餘好處。下面主要講一下各類收益中的最重要的兩個。

快速上線 是什麼概念呢,前面說了幾十個業務和在一個項目裏,一塊兒發佈,這樣發佈的頻率能有多高?實際考察一下放開限制後的情景,就會發現有超乎意料的高。咱們的一個微前端應用的業務是頭條號,它在 2019 年上半年發了 2000 個版本。前面說了傳統上線須要 30 分鐘才能完成打包升級和容器的重啓,而且 10 分鐘才能完一個回滾,這就意味着 1000 小時的上下線等待時間。相比之下咱們新的方式點一下 HTTP 請求發出去就生效了,是一個毫秒級的反應速度。

這個擱之前就不是慢、須要乾等着的問題了,直接你們就不這樣去發了。都學 Native 發版那樣火車式發佈。結果是響應效率下降了不少,不少需求漸漸變得再也不由開發造成瓶頸,反而是總要等版本排發佈。

獨立切換 咱們如今就分別發,能夠一個單頁應用分幾十個模塊,各自上各自的、下各自的。並且後面會說到還能夠各自配置本身的 AB 測試版:有 10 個模塊就能夠產生 1024 個 AB 版的組合,20 個模塊 100 萬個。跟之前徹底不敢想象——也就是說一塊兒發版的時代根本作不了這個事。如今不敢想象的反而是,你說字節跳動某個業務裏面不能作 AB 測。

咱們的頭條號平臺就是剛纔一個典型的微前端項目,包含列出的這麼多模塊,各模塊有獨立的版本,和對服務版本的 session 控制。每一個模塊進去都是版本列表,有一個模塊全部的歷史版本。經過這個平臺配置小流量、AB、上線規則。

運行隔離

1. 耦合開發的嚴峻形勢

17 年咱們推動項目的時候有一個很不錯的帖子很流行,紅遍朋友圈那種,講 react-loadable 的。ta 從解耦的維度介紹了這個方向。咱們當時也有一個很明確的業務需求,要把公司不一樣部門的人組織到一個項目裏。而且這個項目通過經年累月的增肥,已經很是臃腫而且積攢了不少值得推敲的、非直接技術的工程細節。這就意味着要用不一樣組織,不一樣的技術,不一樣的工程規範和打包工具,去合寫同一個平臺、同一個工程。若是當時用了 iframe 可能就是很是湊合的勉強知足業務,徹底不符合咱們追求極致的習慣。

而後當時咱們很在乎一點就是這種跨團隊合做,想融合不一樣的技術團隊,實現少費力溝通或者不重溝通,運行隔離是個很是絕對的基本前提,咱們其餘分享裏面也用了不小的篇幅介紹,有對內的也有對外的。當時的效果是什麼呢。這個是咱們 18 年 4 月內部培訓錄製到的當時狀況:

咱們把線上的頁面(左圖)經過調試工具插入腳本,臨時移除掉沙盒功能,獲得的右圖效果。

2. 運行隔離的目標

運行隔離是啥意思,回想一下剛纔說的 AB 測的問題,20 個項目是多少個組合。若是把這個對應到 bug 的維度,你們都在一個應用裏亂跑會有多恐怖。那麼這樣的組合對咱們的程序和程序員提出了什麼樣的要求?

不跑掛 說是「對一切工程師最基本要求」,我以爲不算誇張。全部軟件工程師的第一個能力層級都應該是不把系統拖垮。微服務以後這個問題不明顯了,由於有架構層面的方式解決了絕大多數挑戰。我很信服的一個理論是全部程序員都是四個階段:寫完需求,不拖垮別人,能擴容,性能好。

不干擾 也是另外一個大問題,咱們當時西瓜團隊和頭條是兩個獨立的 App,他們和咱們的合做徹底跨部門,連 polyfill 的規則都不同。事先也是作了不少公共組件、 CSS 約定之類的。可是規範和約定遠遠不夠。協做的境界從最差到最好應該是:

  • 定規範:誰來了都好好學、好好聽,本身對本身的行爲負全責。
  • 能 enforce 規範:不憑自覺,而是用工具和流程等手段去發現和強制,實現可靠性。
  • 不須要規範:系統的肯定性由系統解決。靠人去發現和執行規範是消耗大量認知資源的,帶來的都是額外的工做量和系統的不肯定性。

3. 沙盒

咱們還有另一篇文章專門介紹沙盒的設計和採坑經驗。這篇就快速用幾張圖示意一下。

① 變量保護: 全局變量、 DOM 和 CSS 基本都是走的這條路。先後兩次快照,咱們來比較,以後根據須要幫你恢復現場。這塊內容不細說了,看一眼圖就不言而喻:一次比較對照全部 key、兩次遍歷、黑名單 location、白名單 readonly。估計我這樣一說你們都懂。

② 沙盒時序: 稍微多說一些。右圖是咱們作的 ABCDE 五個模塊的加載和混行的時序圖。虛線左邊是加載,右邊是獨佔線程所佔用的時間。也就是說有 ABCDE 五個模塊五個沙盒,分別在這個模塊編譯(下載、建立 js 變量和函數、運行這些語句、最終生成一個 React Component)和運行時(這個模塊被打開、渲染對應的全部功能)。

這裏面兩個基礎:js 單線程、事件循環

咱們用了很是單純的單進程操做系統的思路,比喻一下就是 js 的單線程就像單核 CPU 同樣。你激活一個模塊,至關於激活一個線程,其餘都退到背景裏。

實際上單核單進程不是必然,你們都知道這個原理。在事件循環的基礎上,咱們能夠封裝全部的異步操做,把回調套在沙盒激活後面。好比 setTimeoutaddEventListener,這樣每一個模塊看起來就像是在並行。這塊能夠說的不少,可是就想一下操做系統的比喻就行了。

4. 加載方式

React 的項目用 react-loadable 自己很少說了,VUEraw (也就是不包含展現層框架的原始版)的各類項目,咱們都提供 masterpage 的樣例,每一個版本對應的都實現了一套和 react-loadable 類似的效果。

子模塊(Modules) 就是一個個的 CMD 包,我用 new Function 來包起來。其餘就是具體主工程(MasterPage)項目框架的約定,load 過程分爲 5 個鉤子:

  • preload 是否預加載,是個 promisefullfill 的時候就會觸發 Ajax。各類空閒政策阻塞政策均可以由 master 制定;
  • loadCondition 編譯前置條件,fullfill 了纔會開始運行這部分,執行結果就是獲得那個 CMD 的 exports
  • provider 是一個模塊的入口的函數,由模塊開發者提供,返回模塊的一切輸出。這個函數的傳入參數由 masterpage 主工程來提供。
  • loaded 完成加載,獲得編譯結果了。
  • 等等後面不說太細了。

環境一致

由於咱們以前都是在講微服務是什麼和落地效果如何,歷來沒有講過 推行一個微服務你得作什麼。如今這部份內容是咱們第一次公開分享的,也是一個很獨立的維度。

其實就是在講爲何對微前端來講這個環境一致工具是必須的,是繞不過的必經之路。若是不搞也很容易就栽進坑裏,項目失敗。而後極可能還不知道是爲什麼失敗的,把問題歸結爲框架很差啊、人很差啊甚至微前端就很差啊之類的問題上。

1. Serverless vs container

container 就是一個寄生環境,儘管這個環境仍是挺特殊的,不像 linux 這種完整操做系統。相比之下 Serverless 就特殊多了。特殊到連谷歌雲都曾經在商業上被擊敗。

這裏舉兩個 Serverless 的例子,好比 lambda。它的本地工具是一個 CLI 系統:SAM,是一個很是典型的必要基礎設施。若是說發展容器化 AWS 全是靠 docker,發展 lambda 就是靠的 SAM。

另外一個典型的例子 firebase。想必前端的同窗們都很是清楚,也都用過他們的開發套件。這些工具都很是重視一點就是本地開發,我作個項目到底能不能先測試再上。或者說先調試在上。

要作的話就是儘量模擬真實環境了,SAM 的話就模擬了 API Gateway,memory limit 這些。有 live debugging、 local debugging。否則的話發什麼瘋有人敢把線上業務放到一個很是不一樣的環境下運行。

2. 你是否是環境有問題(在我這是好的)

標題程序員最常說的一句話對不對,另外一種表達是「我這是好的」。你們都知道絕大多數狀況這麼說話不對,但經常會忍不住說。甚至更像是實際上是對本身說。一種捫心自問,自我拷問,「我這是好的啊」。

咱們用沙盒把微前端作成了像 container,像瀏覽器裏的 docker。可是不夠,咱們仍是把寄生在 masterpage 內這種應用框架的特徵,也就是業務具體邏輯,當作是一種 Serverless。

而後咱們還把隔離的思路作到極致,咱們的 dev 命令是經過啓動參數啓動一個徹底獨立的 Chrome 會話,有本身的 cookie 啊緩存啊這些,效果像是裝了 2 個 Chrome 乃至多個 Chrome。而後代理工具默認也配到了啓動參數,是個 pac 文件。因此也能夠單獨用或者裝 switchy 用。

代理工具 就是調試環境的整個配置,那些走測試環境、哪些走線上請求所有代理管理。生成一個動態的 pac 地址和代理服務。就是剛纔說的。

關鍵請求,好比服務發現的請求,顯然是代理掉的。走一個咱們爲本地環境定製的返回值。更細節的功能是咱們能夠協助調試主工程(MasterPage)、 組合上某個 module,你也能夠用指定的 MasterPage 版原本調用你正在編寫的模塊。

你也能夠指定是否加載完整的線上模塊列表、只替換你正在調試的模塊。

咱們也還有完整的植入 webpack dev server 的服務供選擇。前面說了支持任意打包工具,這塊是解耦的,只不過你用了咱們能夠幫你 reload,部分刷新動態刷新。後面再細說。

發佈檢查 是針對服務註冊這一塊。這塊的一部分,咱們的 build 命令有一套檢查,對應 git 鉤子。

方便調試 咱們還在必定程度上支持了 HMR。咱們能夠像開發一個普通前端應用同樣開發主工程(MasterPage)和子模塊(Module),子模塊更新後改變模塊管理器狀態,並由內置的 eventbus 機制來從新渲染 HMR,這個機制也能夠用到盛傳環境。

咱們的公共庫能夠經過在 MasterPage 項目裏引入、子模塊裏 external 的方式實現模塊間共享。 也支持子模塊使用特定版本的基礎庫。

Vue 用到了全局變量及原型鏈擴展,暫時還不支持 Hot Reload 的調試。

其餘的框架優點

框架上看就是 serverless 的方向。不是真的 serverless 是前端 serverless,業務 module 開發者不少東西都不用再關心了。舉個例子就是 console.log。如今你們都知道線上業務要乾乾淨淨體體面面,把 console 都收拾整齊。這是咱們以前提到的規範的層面,咱們能夠作到吸取全部 console,存儲錯誤堆棧。而後用戶反饋的時候做爲 trace 元數據提交到反饋後臺等等。

這些都是 masterpage 層面的框架了。固然不是必然關係。可是能夠說微前端給了一個很是方便像這樣組織項目的渠道。

咱們線上的 sourcemap 也是根據服務發現的管理後臺權限控制的,只有開發者能看。

下一代前端展望

前面講了,服務發現是一個對前端可用資源的整體管理。這個能力是不侷限於運行時的微服務前端的。對一切資源都適用,下面說一下這塊。

服務發現 + CDN

抽象一個完整的前端訪問,首先拆成 3 步:A 頁面加載,B「服務發現」,C 根據服務發現結果加載資源。那就有不一樣的變種。最直觀的就是 AB 結合,SSR 畫上去,把 html 請求下來,module list 資源列表已經全了。這個系統咱們這代號 GOOFY。固然也能夠 ABC 都組裝進去。這個後面細說。

另一個思路就是 BC 結合,我請求一個列表,不用說我能夠把 js 內容都 combo 進去。少一些額外的請求。

總之大意就是這個 ABC。

Token 解析

中心服務從中心機房把規則心跳給邊緣節點,邊緣節點接收客戶請求,就近解析出基本的 token,這個過程當中不依賴其餘服務。

這個 token 由一樣在邊緣的頁面服務提供。由於脫離了到中心機房驗證的步驟因此 token 時效有必定依賴前端 SDK。

高可用

高可用能夠說是邊緣計算的一個極大的好處,額外給了咱們一個收益。這套系統的容災基本等同於智能 DNS 對應的探針保活這一套成熟技術了。

咱們須要的就是把邊緣節點心跳到一個監控服務上,他們會分鐘級動態修改 DNS。若是沒有足夠的邊緣節點生存,還能夠 DNS 到傳統的中心機房。

這樣絕大多數流量都不須要進出中心機房,資源都是就近的、多播的。

結尾

以上就是本次分享的所有內容,咱們從落地的細節分享了字節跳動兩年來使用微前端的經驗,以及面對這些挑戰時的思考過程。很是幸運咱們的項目有足夠多給力的夥伴們支持,最終得到了比較大的成功,也很是明顯地提高了重量級的產品的質量。

微前端和不少前沿和剛剛發展的概念同樣,自己還在快速的演進和驗證的過程當中,咱們的具體實踐也一直在快速的變化,在不斷地發現弱點和糾正它們,也在努力發展更多的可能。在這個從種種不完美到更完美的奮鬥過程當中,能給讀者分享咱們的成果是咱們的一種榮幸。並且在分享後,若是能收到指教、討論和建議咱們會更加感激,而且很是歡迎。也歡迎更多的有識之士加入咱們,具體可參見 內推連接

相關文章
相關標籤/搜索