大家好,我是若川。19年我寫的?lodash源碼?文章投稿到海鏡大神知乎專欄竟然通過了,后來20年海鏡大神還star了我的博客,同時還轉發了我的微博。時間真快啊。今天分享這篇Node.js的討論。
2021 年上半年早已過去,回顧 Node.js 在國內的發展和生態建設,我們積累了一些經驗,摸清了一些方向。在所謂的「Node.js 后框架時代」,其技術發展將隨著語言演進和整體前后端技術架構的升級,將會搭上高速快車。
這篇文章,我們采用實錄(AMA)的方式,就和?@天豬?關于「Node.js 框架設計及企業 Node.js 基礎建設」相關話題的討論內容進行總結。接近 90mins 的線上討論,這篇文章相信對前端開發者,尤其是 Node.js 開發者會有所啟示。
交流概覽
EggJS 官方站點
分享人介紹:阿里-天豬
相關周邊:
如何評價阿里開源的企業級 Node.js 框架 EggJS?
EggJS 一個討論
Egg & Node.js 從小工坊走向企業級開發
分享人介紹
天豬,Egg 開源接口人,cnpm 接口人。隸屬于螞蟻體驗技術部 - 廣州分部,負責螞蟻 Node.js 基礎設施建設,包括 Chair、TNPM、BaaS 服務等。日常工作比較跨界,涉及到研發平臺,PaaS,框架,包管理。
分享內容介紹
分享內容我們按照:
Node.js 框架設計相關
Node.js 方向相關
生態發展和開源社區相關
三大方向展開,每一個方向對應若干討論話題/問題。下面展現部分交流內容。
Node.js 框架設計相關
話題一,關于 Node.js 框架設計
如何看待其他 Node.js 框架,如 ThinkJS, fastify 等?如何看待和對比 farrow-js,函數式風格的 Node.js 框架,相比較而言設計理念上差異還是很大的。
天豬認為,
定位不一樣, Egg 的定位是框架的框架,面向的目標用戶是團隊的架構師或技術負責人,幫助他們來定制適合特定的業務場景的上層業務框架。解決的是大規模企業級開發時的生態共建 + 差異化定制問題。
像 Chair, Midway,beidou 這些才是面向一線應用開發者的上層框架,我們今年也會開源一個 TEgg 方案,提供官方的 TS 方案,以及業務邏輯復用能力。
從天豬的角度看,完全可以用 Egg 封裝出一個 ThinkJS 類似風格的框架。
編者按,
不管是 koa 還是 express,它們都屬于輕量級的 Node.js 框架,它們使用起來優點明顯:使用簡單,中間件機制靈活,社區生態完善。
但是在實際使用中,隨著業務復雜度上升,以及不同場景下對于 Node.js 框架的不同訴求,在直接依賴 koa 或 express 的實踐中我們發現了一些痛點,包括但不限于:
需要業務手動配置各種中間件
在上一條條背景下,一些基礎通用的中間件缺少統一抽象和維護
容易出現重復代碼以及各造輪子(重復中間件邏輯)現象
koa 和 express 并不約束項目規范(比如目錄結構等),不同水平程序員搭建的 Node.js 項目風格不統一,質量參差不齊
koa 和 express 并不直接解決 Node.js 項目的基建部分,不解決調試、開發配置等關鍵環節,使得生成效率難以最大化提升
為了解決上述問題,社區上出現不少基于 koa 和 express 的上層 Node.js 框架,比如:
阿里體系基于 EggJS 之上的 Chair、Midway 等框架。
Spring 風格的?NestJS
360 奇舞團的?ThinkJS
這些框架在不同程度上封裝了 koa 和 express,并給出了各自風格的上層方案,但都也不完全是一個開箱即用的企業級 Node.js 框架,不能直接滿足 XX 企業內場景,難以直接融入已有 Node.js 項目,理想情況下,仍需一道封裝。
此外,社區上還活躍著著:fastify,farrow-js,restify,hapi,sails 等 Node.js library/framework,各自側重點(性能優先/易用性優先等)和分層領域(low level/high level 等)各不相同。
對于一套企業級 Node.js 框架來說,設計一套 Node.js MVC 開發框架,或在 Egg 之上再封裝,其目的是保障易用性、穩定性的基礎上,提升 Node.js 項目開發的效率及服務性能。
話題二,一個關于 Node.js 框架性能的討論
EggJS 文檔中關于特性描述,提到了「基于 KoaJS 開發,性能優異」。其中,前半句「基于 KoaJS 開發」和后半句「性能優異」構成因果關系嗎?如果構成,KoaJS 帶來的性能收益體現在哪里?如果不構成,那么 EggJS 關于性能方面做了哪些事情?(除了 co 升級為 async/await 之外)
天豬認為,
沒有因果關系,這里當時只是陳述 Egg 的 2 個特點,在那個時間點來說,Koa 在社區的認知里面還是屬于先進的(那時候還是 Express 的中期)。
其實對于大部分的應用來說,框架遠遠不會是性能的影響因素,性能更多可能出現在和后端通訊的序列化/反序列化,不合理的調用鏈,耗時的 CPU 操作等等。
Egg 宣稱自己有優異的性能的底氣在于:在過去 9 年來在線上雙十一量級的壓力場景下的穩定表現,絕大部分性能問題都能被發現并逐一解決(AliNode 利器),舉個例子,最近語雀居然發現 Router 成為了性能瓶頸之一(1000+ 路由,20ms)。
同時豐富的實踐也讓我們很清楚一個事實,就是大部分的性能問題,都輪不到框架來背鍋,很多都是業務不合理使用。因此我們在性能上優化主要不體現在 Egg 框架本身。比如,fasitfy 基于 Schema 來優化序列化的性能,這些好的社區實踐我們也一直在吸收,如 sofa-hessian-node 針對復雜對象場景的性能有明顯的提升。
編者按,
Egg 方面通過相關 benchmark,發現去除 co 后堆棧信息更清晰,能帶來 30% 左右的性能提升(不含 Node 帶來的性能提升),但對于 Egg 本身來說,框架并沒有著重發力性能細節,沒有像 fasitfy 等框架內置性能機制優化操作,對 perf friendly 錙銖必較。
事實上,對于一個 Node.js 服務來說,服務的響應性能瓶頸往往在業務工程當中,在框架設計方面,使用便利性和性能的平衡是一個永恒的話題。當然,社區上也鼓勵和歡迎任何一性能為「賣點」的框架。
這里希望大家注意的是,脫離業務的性能優化——只會是框架本身的「自嗨」。像 sofa-hessian-node 案例一樣,由業務中發現性能痛點(后端交互的序列化性能瓶頸),再去借鑒業界的相關實踐,最終業務和技術相互促進,框架在各個層面也會得到更多發展,是一個非常可貴的相互促進結果。
話題三,仍然是一個關于 Node.js 框架性能的討論
EggJS 加載器的設計,看上去在啟動時并不很性能友好(比如 Egg 會遍歷項目中的 loadUnit,再比如插件的加載過程)?這方面有相關的設計考慮嗎?
天豬認為,
在 Egg 發起時那個時間點,甚至包括現在,啟動的時間其實沒那么重要,因為都是集群部署的方式來分批滾動升級的。
一般應用插件也不會太多,我們最復雜的場景 Chair 內置的插件近百個,讀取文件的耗時其實占比很小,更多的啟動耗時在于某些插件需要和后端中間件服務建聯,拉取配置等等初始化上。所以上面提到的『遍歷目錄』這個其實不會對性能有影響,而要看插件本身的自有邏輯復雜度。
Serverless 時代,做極速啟動時可能就會有一定的影響,這塊可以通過構建期的預分析,來幫助框架減少文件系統的遍歷,但收益不一定大。
編者按,
Node.js 框架啟動性能往往是一個被忽略的話題,但在容器化和 Severless 大勢發展下,啟動速度逐漸被開發者所關心。這一方面,我們將會持續關注相關話題。
話題四,一個關于框架漸進式設計/發展的討論
EggJS 內置了 static 服務插件,在 EggJS 內置哪些插件/能力的設計上,考慮有哪些?(比如為什么 static, i18n, logger 作為內置插件出現,這些能力像其他插件一樣做成可插拔的不更好么?
天豬認為,
由于歷史原因,當時 Egg 開源的時候沒有拆的很干凈。其實可以看下 egg-core,它其實才是 egg 的最核心部分:
Koa 在 Node HTTP 之上提供了一層很薄的語法糖封裝,并提供了洋蔥模型。
egg-core 在 Koa 之上,提供了一套 Loader 機制,并基于它延伸出了插件和上層框架。
特定場景框架:chair-serverless | midway-faas | ↑ ↑ 團隊業務框架:chair | midway | nut | ... ↑ ↑ ↑ ↑ 阿里統一框架:@ali/egg ↑ ↑ ↑ ↑ 開源社區框架:egg
事實上,Egg 的應用、插件、框架的目錄結構幾乎一模一樣。
實際開發過程中,我們也有一套?漸進式的演進方式,分享給大家:
實驗性的功能,可以先在應用里面實現,作為 inline plugin 通過 path 方式來掛載。
功能穩定后,就抽出來變為獨立的插件,應用再通過 npm 依賴方式引入,只需改兩行代碼即可。
當該功能成熟后,成為團隊的統一規范時,直接把這個插件集成到 Framework 中,所有應用只需重新安裝下依賴,即可立刻享受到。
這個過程是閉環的,是漸進式,而且升級過程幾乎無痛。
編者按,
對于任何一個框架的設計來說,漸進式是一個重要的課題。
比如 Vue.js,比如 React.js,漸進式的設計能夠保證業務開發者更敏捷地迭代,更方便快捷地使用框架,同時依然保留有對架構進行優化、基礎能力進行下沉的能力。對框架設計者來說,漸進式的設計,更是保障框架本身進步發展的關鍵。
話題五,一個關于框架的上層封裝討論
關于基于 EggJS 的上層框架方案,有沒有 Best pratice 建議?比如,Chair 基于 EggJS 的封裝,做了哪些「有趣、不一樣」的事情嗎,有哪些值得借鑒的嗎?
天豬認為,
上層框架是針對特定的業務場景 或 特定的基礎設施 的封裝,而隨著一波又一波的需求過來,各種階段性的妥協,基礎設施的演進,它注定是維護和治理的重災區。
一個新的插件,該不該內置,如何漸進式的推進,又如何在它被另一個插件替代時,推動下線。業務場景變化的時候,框架該不該多套娃一層。鎖不鎖版本,如何推動更新,出現問題如何快速止血,等等對于這些問題,框架的應用規模是 10 和 1000 時的思考點是不一樣的。
話題六,關于設計遺憾和未來計劃的討論
關于 EggJS 的進程管理問題,以及相關部署、重啟等環節,在容器化 docker 化的背景下,EggJS 的相關設計是否需要跟進和調整?
天豬認為,
Egg 的 agent 提出的背景,是后端中間件服務曾經被我們 ddos 了,當時好像是 diamond 這個遠程配置服務,我們每一個進程,都會在啟動的時候向它拉取配置,所以需要有個 agent 來統一做這事,拉取完后再共享給 workers。
在 Serverless 的大背景下,類似的配置拉取其實有云原生的內置方案,不需要框架層過多考慮。我們也確實有考慮單進程模型,并在內部也有一些實踐,但目前沒有看到特別的收益。
在 Egg 3.0 里面,cluster 不會內置,單進程模式也能對 mock 和單測這塊帶來更清晰的邏輯。
EggJS 有不適用的場景嗎?是否有在設計上的一些遺憾?未來有哪些計劃?
天豬認為,
受限于 Koa,目前更適合于 Web 框架。在 3.0 Context 模型后,這塊應該會更靈活,不再受限。據我所知,有不少開發者用來當隊列處理器,或者任務執行器。前者不是不可以用,只是總會覺得有一點別扭。后者其實 FC 這類函數計算會更合適。
Egg 3.0(egg-core) :
會更純粹,一套 Loader 機制 + 洋蔥模型,面向未來。
Loader 的生命周期支持異步(從而可以支持異步配置)
不依賴于 Koa,可以脫離 Web 框架這個局限,我們需要的只是洋蔥模型,可以自行實現,核心是一個 Context,根據不同的流量入口,實例化為 HttpContext、RPCContext、WSContext 等。(我們對 socket.io 等的支持就不會看起來太別扭。)
以上是 Egg 的后續規劃
編者按,
很少有一種技術設計,能夠完全開啟「我在 XX 年后等你」的視角,關于 Node.js 的單進程/多進程模型等諸多話題的背后,其實是基礎服務設施的變遷,編程模型趨勢的演進浪潮。這一方面,前端開發者將會越來越多的「跨界」,也只有邁出更多步,才能發揮更大的價值。?一個 Node.js 框架設計實在是整個技術鏈條上的微觀的小環節,但管中窺豹,我們的邊界在更遠方。
下面,我們進入整個 Node.js 方向上的討論。
Node.js 方向相關
話題一,關于下一代 Node.js 框架和 Node.js 趨勢
未來(下一代) Node.js 框架會有什么樣的發展趨勢?你們團隊目前探索的方向是?(比如 BFF -> SFF?)
天豬認為,
框架主要體現在研發模式這塊,涉及到用戶編程界面層。框架是一個重要的點,但不是全部,藏在冰山之下的還有整個研發生命周期(研發平臺、迭代模型、部署、前后端研發模式、監控體系等等)。
Node SDK 這塊未來會 ServiceMesh 化掉,它們應該回歸后端中間件團隊的工作范疇,畢竟由后端來維護自己的服務對應的 SDK 才是最合理的分工。
一個團隊的業務,可能一下子不需要 Node.js,可以不用,但團隊必須具備隨時可以有的準備,沒有這個儲備,你們的技術選型視角和話語權會完全不一樣。Node.js 是前端的一個可選能力,也是一個必要的儲備。
如上圖,Node 的一部分場景是聚合邏輯層,會往云端一體化方向發展,另一部場景是會走微服務方向,跟后端的框架沒啥區別,當今的業界認知是跨語言方向。
話題二,關于 APM
關于 APM,我們知道 AliNode,除此之外還有其他建議或者經驗嗎?關于 AliNode 目前發展情況,比如 runtime 替換掉 Node.js runtime,這種方式是否會成為趨勢,阿里之外有比較成熟的應用嗎?
天豬認為,
可以考慮用一君的 easy-monitor,優點是:
比 AliNode 更低的維護成本,通過 Addon 方式,而不是 Runtime 方式,從而不綁定 Node Runtime 升級,更快的適配。
支持私有化部署。(雖然 AliNode 的 agent 上報是開源的,但人與人的信任還是很難的)
開源。
說到 APM 這里,其實我們內部曾經有過一個有趣的討論:Serverless 后, APM 還重要么?反方觀點:內存泄露就內存泄露唄,小的泄露無所謂,到了閥值,直接動態騰挪換機器。我個人認為 APM 還是很重要的,工具本身很重要,它是查問題的利器和底氣。直接騰挪是實際運維的 ROI,只是代表了某個時刻,暫時可以不用費精力查問題,但不代表問題不應該被解決。
編者按,
重點再聊聊 APM,上面的辯論(「Serverless 后, APM 還重要么?」),反方其實是在靠中臺能力來降維規避問題,這背后其實是一個技術態度問題。
Node.js 服務自己的問題,還需要自己來解決。那么出現了 Node.js 方面的問題,如何借助 APM 來進行調試,發現并解決問題呢?我們又積累了哪些實戰場景經驗呢?請關注我們,后續會帶來更多線上真實案例。
話題三,關于 Node.js 原生 http 模塊的一個討論
EggJS 基于 urllib 實現了一個 httpclient,這個考慮是什么,性能嗎?接上問,EggJS 既然實現 httpClient,那么如何看待 Node.js undici,似乎是一個更貼近官方的方案?fastify 一開始就在使用 undici, 如何看待主打性能的 fastify,Node.js 實踐經驗上,性能是否是比較棘手的一環?
天豬認為,
因為 urllib 是我們實現的,在這么多年的實踐中,踩了 http 的無數坑,跨過之后沉淀到 urllib 里了,多年后它的穩定性我們無比相信。所以目前重度使用了 urllib。undici 最近也有關注到,也稍微用了下。從現在這個時間點來看, urllib 的代碼確實有點亂,我也兩度想重構它,但 ROI 不是很高,就沒下手。未來也許有機會把它重構掉。
編者按,
Node.js 及其相關的基礎建設決定著 Node.js 落地情況,也決定了 Node.js 最終價值。這主要可以分為兩個方向:
Node.js 本身語言演進
Node.js 對接中臺基礎
其一 Node.js 本身語言的演進,
比如,undici 提供了 Node.js core http 模塊的代替方案,帶來了性能和穩定性的提升,解決了 Node.js core http client 歷史的重大技術債務。
另外 Node.js 對接「更后的后端體系」,是基礎建設的重要一環,比如 ServiceMesh 生態。
舉個例子,通過 Sidecar 模式我們可以在 Kubernetes Pod 中,在原有應用容器旁邊運行一個伴生容器,由 Sidecar 接管進出應用容器的所有流量,實現 just work 的應用容器監控。
但應用容器本身的運行細節,如 CPU Profile,Memory usuage 等依然需要 Node.js 開發者給出答案,因此有了 AliNode,也有了不侵入 Node.js runtime 的 Easy-monitor。
對于企業級 Node.js 解決方案來說,不妨經常反問自己:「我們還缺什么,我們還能做些什么」。畢竟 Node.js 基礎建設的發展始終在探索中前進,在這方面,希望每一名開發者貢獻力量。
生態發展和開源社區相關
話題一,鎖/不鎖版本,ESM 和前端生態協作鏈
2021 年,對于版本管理和 Node.js ESM 有哪些新的看法?(比如鎖版本,Node.js package 對 ESM 的支持程度)
天豬認為,
鎖版本 vs 不鎖版本,一個永恒的話題。天豬的觀點是,
不同團隊基礎能力下的權衡,沒有銀彈。
鎖版本是一個保守的方案,擊鼓傳花,把炸彈留給后人。
前端依賴和后端依賴是 2 個話題。
事實上:等有空了再統一升級,往往是一個美麗的謊言,這個在工程上的可執行性不是很看好。
這里附上舊文一篇:為什么我不使用 shrinkwrap(lock)?,后面天豬會寫一篇新的。
繼續這個話題,在職責分離維度,
框架層面可以考慮鎖自己的子依賴,框架維護者來負責推動升級,應用開發者鎖自己引入的依賴,職責分離。但這一點上,需要 npm 工具和研發平臺上的緊密配合。
給框架維護者提供一種鎖內部版本的機制,且能由框架維護者來主導應用的框架版本升級
當然提供 CITGM 機制來幫助框架維護者回歸,也是很有必要的。
這里附一個「彩蛋圖」——「研發平臺」中關于版本和生態的「儀表盤」:
我們不妨繼續探索「研發平臺」,即基于研發平臺的依賴生態解決方案:我們給出設計如下,
本地、開發環境,不鎖版本。
信息公開,每次迭代構建后,依賴的更新情況要讓開發者清晰。
配套的止血機制,未來需要更智能化。
緊急迭代,測試 → 預發階段,允許開發者在平臺鎖版本。
更多信息:
編者按,
在生態發展和開源社區相關部分,我們和天豬重點聊了「鎖/不鎖版本」的問題。
這個問題雖然老生常談,但他絕不是一個包管理方案選型的問題,「鎖/不鎖版本」的背后,何嘗不是整個前端社區生態。這方面,不同團隊給出的不同答案,都在詮釋著對前端社區生態的不同理解。
同時,我們也期待?關于現代包管理器的深度思考——為什么現在我更推薦 pnpm 而不是 npm/yarn?之后的 TNPM,以及不斷進化的前端技術潮流。
話題二,關于項目開源和普及度
EggJS 在阿里內部是如何推行的?推行當中遇到的最大困難是什么?(可以再透露下目前內部哪些系統使用了 EggJS 嗎)
天豬,
好像沒怎么推行,就普及的,天時地利人和吧(編者按,好凡爾賽!手動狗頭)
2015.10.13 內部拉通 Node 工作組,閉關一周,產出一份 RFC,基于 RFC 產出 @ali/egg 1.0
2016 年 JSConf 開源,年底的時候差不多幾個大的 BU 的上層框架都重構為基于 egg 了。
目前你能看到的阿里系的頁面,絕大部分都是基于 Egg 的,像螞蟻大部分流量入口都是基于 render 這個高性能的渲染服務(螞蟻森林),天貓那邊的斑馬,還有財富的好幾個大的 BFF 有數千個 POD
Node 是阿里的第二大語言,而 Egg 是唯一的官方框架,不是基于 Egg 的框架,無法跟內部的各大中間件交互,也無法很好地被研發平臺和監控平臺等支持。阿里有數千個 Node 應用。
語雀 - 可能是西湖區最大的 Node 全棧應用。
目前這個時間點會遇到一些問題,整個大環境變了,所以 BFF → SFF
總結
不管是 Node.js 還是其他前端技術方案,我們都期待著一個更友好開闊、更互動交流的氛圍。我們和?@天豬?的交流,名義上是「 Node.js 框架」,其實更是對 Node.js 甚至前端未來發展的討論。
個人力量太過有限,但我們對技術抱有理想,也尋求更多的交流、共創。????
關注我們,后面將會有更多前端技術方面的想法和積淀產出!
Happy coding!
最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。
推薦閱讀
我在阿里招前端,該怎么幫你(可進面試群)
畢業年限不長的前端焦慮和突破方法
前端搶飯碗系列之Vue項目如何做單元測試
老姚淺談:怎么學JavaScript?
·················?若川簡介?·················
你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,活躍在知乎@若川,掘金@若川。致力于分享前端開發經驗,愿景:幫助5年內前端人走向前列。
點擊上方卡片關注我、加個星標
今日話題
略。歡迎分享、收藏、點贊、在看我的公眾號文章~