微前端架構:如何由內而外取代單體架構

微前端架構:如何由內而外取代單體架構


image.png

做者|Zack Jackson譯者|核子可樂、無明編輯|王文婧如何利用微前端技術實現單體應用程序的現代化改造?在本篇教程中,咱們將探討如何將前端從單體架構當中剝離出來,並快速完成微前端架構遷移。本文做者將結合我的項目實踐經驗爲你們介紹心得。問題所在css

咱們假設有這麼一個單體代碼庫,它使用了某種後端模板引擎或者系統(例如 EJS 或者 ERB),但沒有認真考慮前端的設計需求。或者更糟糕的是,前端的開發早於 SPA 的出現,或者還可能使用了相似 Ruby on Rails 那樣的框架。所以,JavaScript 文件(例如.js.erb 文件或 AEM 片斷等等)中可能包含了後端變量。這種粗製濫造且各組件間緊密耦合的代碼庫幾乎沒法進行現代化升級。html

咱們固然但願不要再在這個單體系統中開發前端代碼,咱們但願轉向更加 JavaScript 化的生態系統——但具體該怎麼作?前端

大多數企業都負擔不起(或者不肯負擔)這種因工具淘汰帶來的重寫成本與停機時間。功能的演進須要開發的支持,但要保持一樣的速度開發這些功能顯然愈來愈困難。webpack

正因如此,咱們應該經過一種漸進且平滑的方式將單體逐步拆分爲更多較小的部分,同時保證不讓業務發生中斷。git

說來簡單,但單體架構的拆分過程至關棘手。在進行前端遷移時須要爲支持 JavaScript 的應用程序規劃和開發新 API,拆分就變得尤其困難。web

在等待新 API 開發和發佈的過程當中,前端迭代開發、微前端(MFE)實現和團隊的自主行動都將陷入僵局。但真的要這樣子嗎?錯!咱們能夠對前端和後端進行解耦,讓它們齊頭並進。image.pngexpress

Zack Jackson — ScriptedAlchemyjson

下面將介紹一種方法,它可以順利解耦前端,並將其移植成具備 *** 的獨立 MFE。如此一來,團隊再也不須要等待後端 API 被拆分紅微服務或者等待後端 API 可用。這個方法叫做「由內而外替換單體」。後端

阻礙因素

微前端一般包含如下兩大重要依賴項:瀏覽器

1) 認證;

2) 提供給應用程序的數據,在瀏覽器端和在服務器端渲染(***)期間。

根據個人我的經驗,不管你的遺留系統屬於 Rails、Java 仍是.Net,用戶身份認證一直是最難與單體後端進行剝離的部分。

將單體做爲佈局引擎

MFE 有多種不一樣的架構規範。本文將重點介紹其中一種,即在後端微服務中很是流行的一個版本——LOSA(Lots Of Small Applications,大量小應用)。對於「由內而外」遷移來講,這是最理想的選擇。image.png

流經單體的 LOSA 請求 / 響應流

LOSA 應用(一般爲微前端)屬於獨立的 Node.js 服務,可以在服務器端渲染網頁的一部分或者某些片斷。每一個頁面能夠由多個 LOSA 服務組成。這些應用程序 / 微前端單獨進行構建、部署,並運行在容器中。image.png

上圖所示爲同一頁面採用了三種不一樣的渲染方式,演示了一個增量遷移的過程。先是單體渲染頁面,再過渡到 LOSA 微前端,而後變成垂直的微前端。最後,單體被完全替換掉。

固然,單體仍然負責處理 HTTP 請求,並將最終響應發送至客戶端。微前端能夠放在集羣的防火牆後面——僅提供給遺留系統使用,直到 API 網關和用戶身份認證機制剝離完成(或者至少已經轉化爲 API 端點)。在此期間,咱們不須要作太多的改動。

渲染流程

下圖展現了遷移後的請求 / 響應流程。

首先,發出一個請求:
GET/POST 'https://MFEwebsite.com/parts/header?format=json

image.png渲染頁面內容須要各種數據,那些沒法從已解耦端點查詢到的「缺失」信息能夠在請求期間以 props 的形式發送給 MFE。請求會通過一系列中間件,這些中間件負責渲染 React 應用程序,而後調用已解耦的 API,並將響應結果以 props 的形式返回。這些 props 最終將組成 window.INITIAL_STATE。

代 碼

關於模板功能或者過濾器的實現方法,我向你們推薦 Hypernova。不過我本身並沒用過,我已經習慣了一切本身動手,並在 Rails、Node 以及 PHP 後端中實現了相似的機制。但考慮到各種後端平臺都有本身的特色,因此這裏我就用 Hypernova 做爲示例向你們講解。

下面使用 express 實現 MFE 渲染端點:

來自另外一個系統的請求(在這裏就是那個單體):
GET/POST 'https://MFEwebsite.com/parts/header?format=json
{
   html: '
<div> ... </div>',
   css: '
/static/header.3042u3298423.css',
   js: '
/static/header.idhf93hf23iu.js',
   initial_state: {items:[...]}
}
用於處理響應的中間件:
export function exampleRenderAPIware(req, res{
  const renderedMarkup = renderHTMLpage(
    req,
    this.index,
    intial_state,
  );
  asyncRender.then(() => {
    const responseObject = {
      html: renderedMarkup,
      initial_state,
      js: jsResource,
      css: c***esource,
    };
    res.status(200).end(JSON.stringify(responseObject));
  });
}

發出這些初始 POST 請求的控制器也須要處理響應結果,將 JS 與 CSS 放在正確的位置,最後在遺留模板的對應位置渲染 React。以前一般由其餘控制器負責處理的資產如今須要負責將腳本與樣式注入到遺留標頭與 body 標籤的底部。請注意,單體仍然被做爲佈局引擎。咱們也在替換其餘部分,並以 React *** 方式添加新功能。最終,這些 LOSA 應用將經過一個 MFE(或者藉助 Webpack 黑魔法,我本身開發了 webpack-external-import)整合在一塊兒。

如何從模板數據遷移至新 API?

在遷移中,解耦並上線新的 API 到底會帶來怎樣的影響?

以前,在單體把數據傳給 MFE 時,express 訪問 HTTP 的請求正文。而如今,express 向 API 異步獲取數據。雖然數據格式可能會發生變化,但 React 仍然可以正確獲取到 props。

性能差別

與舊單體相比,LOSA 架構的性能還不夠好,一般須要 400 到 600 毫秒才能渲染出頁面的特定部分。咱們採用了異步 Worker 結構,這樣就能夠同時請求多項服務來渲染應用程序的不一樣部分(而不是渲染單個應用)。但這種做法提升了應用下線的難度,由於「生產故障」會致使側邊欄或頁腳部分長時間缺失。所以,進一步拆分纔是最好的選擇。

我所說的 LOSA 異步 Worker 是這樣的:咱們使用大量的 Node 服務,每個服務負責渲染頁面的一個或多個組件。image.png

遺留控制器(圖中的灰色齒輪部分)能夠將視圖數據轉給 POST 請求,而非後端模板引擎。回收數據機制則可以幫助後端減小支持負擔。因爲無需作出重大修改,後端開發人員可以騰出時間,專一於解耦數據服務,而前端也能夠進行獨立的開發。

視圖數據被髮送給了外部的 React 服務,而響應消息(包含了 HTML、樣式表、初始狀態以及 CSS URL)則被髮送給後端模板引擎。如今,模板引擎只須要渲染 POST 請求所對應的響應,從而將視圖或視圖的一部分與原有單體剝離開來。

React 渲染時間

React 真的很慢!*** 也不怎麼快——所以新的 LOSA 架構解決方案沒法帶來理想的性能表現。咱們的解決方案是:在 React 內部進行片斷緩存。image.png

  • 黃色:無 React 片斷緩存——端到端(400 毫秒左右)

  • 深紫:有 React 片斷緩存——端到端(150 毫秒左右)

  • 橙色:全優化架構(20 毫秒左右)

  • 綠色(底部):來自後端的原生片斷緩存

React 優化工做至關複雜,受篇幅所限,恐怕只能另起一篇文章詳加說明了。總之,Graphana 數據顯示,咱們至少將渲染性能提升了一倍,不過輪循時間仍然很長。儘管 React 已經可以在內部快速完成渲染,但 150 毫秒的端到端時間尚未達到咱們的預期。在下一篇文章中,咱們將具體聊聊片斷後端與片斷緩存。

渲染時間 VS 輪迴時間

渲染時間一直是個麻煩事,即便是在 React 中採用了片斷緩存以後,性能仍然沒法使人滿意。令我感到失望的是,雖然 Node.js 內部的渲染速度很快(約 20 毫秒),但整個流程仍然須要 140 到 200 毫秒才能完成。

瓶頸所在

  1. JSON 大小,特別是初始應用狀態——即渲染頁面所須要的最少 state。咱們再也不在初始渲染中放置太多字符串化的 state,只發送足夠讓 React 完成渲染並讓摺疊組件變得可交互的必要 state。
  2. 須要渲染的 DOM 節點數量——再也不將代碼放在無用的 DIV 中,只須要給它們加個 class。利用 HTML 的語義特性以及 CSS 的級聯效果,咱們能夠少寫一些 HTML 代碼,這樣也就減小了 React.createComponent 函數的生成。
  3. 垃圾回收——咱們將在下一篇文章中討論更多細節。
  4. 速度由數據服務決定——在中間層使用 Redis。不少朋友認爲「緩存失效問題難以解決」,我建議各位認真考慮一下事件溯源,或者咱們可使用 CQRS 與異步 Worker 來處理讀寫操做。
  5. 單體架構與 MFE 之間的 HTTP 開銷——也就是 gRPC、CQRS、UDP 以及 Protobuf。兩者之間的通訊應該經過內部 Kubernetes 網絡進行。POST 速度很慢,但也不是不能用。遇到問題時個別處理便可。

如何提高後端渲染性能

簡單來講,模板化、片斷緩存與 gRPC/CQRS,移除 JSON 中臃腫的數據。React 在服務器端速度較慢,但請記住,一切拆分都只會讓速度變得稍慢,而不是更快。

伸縮問題如何解決?對於一切解決方案,若是不能在規模化場景下實現良好的成本效益,那麼都將只是空談。咱們絕對不能容忍天文數字級的運營成本或者糟糕的性價比。大規模且廉價的解決方案纔是好的解決方案。下面來看幾點容易被忽視的成本要素:
  1. 昂貴的第三方服務費用;
  2. 更多 / 更大的容器環境;
  3. 因爲性能不佳而致使的收入損失;
  4. 因爲兩個分支沒法同時被合併到 master,所以單體架構會致使發佈週期或部署流程阻塞;
  5. 開發人員在風險較低的環境中能夠快速行動,業務人員可以將新想法推向市場,並對出現問題的部分及時回滾——快速行動的能力正是實現高成本效益的必要前提。

最終結果

流量: 1000 萬次渲染 / 天

資源分配:
  • 實例: 5

  • 內存: 100mi (100 MB 內存)

  • CPU: 100 (單核)

  • 最大 CPU 使用率閾值: 65%

  • 響應時間:20 至 25 毫秒

  • DOM 複雜度:高

  • 響應時間縮短了 95%

  • image.png
  • 綠色:後端渲染時間

  • 藍色:使用了片斷緩存和 state 優化的 React

個人單線程 JavaScript 應用程序要比使用完整片斷緩存的多線程後端系統更快。

原文連接:https://levelup.gitconnected.com/micro-frontend-architecture-replacing-a-monolith-from-the-inside-out-61f60d2e14c1

 活動推薦

偶發 bug 大大增長了排查的成本,復現也變成了全部研發心中的痛。在即將召開的 ArchSummit 全球架構師峯會(北京站)上,貝殼找房基礎架構中心前端架構委員會專家陳辰將爲你們帶來貝殼自研監控平臺燈塔以外的另外一項目——時光機,揭祕如何利用時光機讓偶現 bug 無所遁形。

點擊【閱讀原文】查看詳情。目前 9 折限時直降 880 元!瞭解詳情請聯繫票務經理灰灰:15600537884 (同微信)。

相關文章
相關標籤/搜索