好未來資料中台 Node.js BFF實踐系列文章清單:
- 基礎篇
- 實戰篇(TODO)
- 進階篇(TODO)
好未來資料中台的Node.js中間層從7月份開始讨論可行性,截止到9月已經支援了4個平台,其中3個平台生産環境穩定,另1個在測試階段近期上線。
我4月份剛加入資料中台,原本的想法是半年内不做大刀闊斧的改變,優先完善團隊現有的基建設施,比如元件庫、charts庫、工具、規範等。Node.js中間層的立項完全是一個意外。
某次中台周例會上讨論到前後端協作效率問題,我一時嘴賤提到Node.js中間層的想法,将應用平台的一些與DB無關的邏輯放到Node層讓前端夥伴們負責,讓後端夥伴集中精力做底層服務建設。
本來就随口一提,結果老闆當真了,當場拍闆:搞!我當時的表情大概是...

沒辦法,自己畫的餅,哭着也要吃完。
咦?怎麼有點往日重制的感覺?曾經在騰訊雲,剛給客戶吹完牛逼就想抽自己大嘴巴~
Node.js 的定位
資料中台 Node.js 中間層的定位類似一層 API Gateway,承載接口代理、聚合以及與DB 無關的部分業務邏輯。
在現階段資料中台的服務體系中有兩類服務:正常 Java 後端和 T-Service 。後者是資料中台将 OneService 方法論落地的統一資料服務,即服務于各個前台事業部,也為資料中台内部的各個應用平台提供資料服務。
使用 T-Service 的協作流程簡單描述就是數倉夥伴建表後将資料源接入 T-Service,然後 Java 後端夥伴配置取數 SQL,最後前端從統一的 query 接口查數展示。
T-Service 不直接對接前端,舊架構體系下需要在前端與 T-Service 之間搭建一層 Java 服務,說白了就是一堆 Controller,從 T-Service 取數後做一些很簡單的二次加工給到前端。Node.js 中間層的目标之一就是将這些複用性很差的 Controller 拿過來,好處有兩點:
- 舊架構體系下完成一個資料查詢功能需要牽涉數倉、後端和前端三方,新架構下隻需要前端和數倉;
前提是前端夥伴需要掌握 SQL,前期由後端同僚輔助,過渡後需要前端夥伴自己寫 SQL。
- 基于第一條, Java 後端夥伴的生産力被解放,集中精力做底層建設或通用性更強的接口。
除了以上兩條以外還有另一個隐藏優勢,前端的能力邊界擴寬後技術視野也會更寬闊,能夠從縱向角度上全面思考業務。
具體到職責配置設定上,Node.js 作為直接與用戶端互動的服務層,登入認證是最基本的功能之一,Java 後端服務隻需要關注 Node.js 傳遞的使用者 ID 即可。
資料中台有一個統一的使用者管理中心提供登入/登出服務,用戶端登入後會接收管理中心下發的 JWT,後續業務接口的請求會驗證 JWT 的有效性。接入 Node.js 中間層以後,JWT 的驗證邏輯就上浮到了 Node 層。
由于 Node 層隻需要驗證(解密) JWT,不需要 JWT 加密算子,是以非對稱加密是相對較優的方案,比如 RS256。使用者中心提供一個擷取公鑰的 API,Node.js 拿到公鑰後進行解密即可。
技術選型
技術選型方面并沒有糾結太久,其實用什麼架構都行,雖然每個架構都有各自的優缺點,但由于 Node.js 中間層的邏輯不會很重,複雜度不高,架構層面的問題幾乎不會成為瓶頸。是以一句話總結技術選型的核心出發點就是:用着舒服就行了。
最終用的 NestJS v7,當然也并不是完全沒有量化的因素。首先跟 express 和 koa 相比,NestJS 的子產品抽象層次更高,将中間件進一步抽象為 guards 、 filter 、 interceptor 等等,能夠滿足大多數場景,幾乎不需要感覺中間件這個概念。雖然有一定的了解門檻,但熟悉之後寫代碼能夠将各子產品的功能劃分更加清晰容易維護。其次,NestJS 與 Express 完全相容,生态足夠豐富。最後,完美支援 TypeScript,搭配 DI 、IoC 等機制,代碼的結構和子產品體系非常清晰。
之是以選了 v7 而沒有用最新的 v8 版本,原因之一是 NestJS 的 v8 版本依賴 RxJS v7。RxJS v7 廢棄了很多 v6 版本的操作符,用慣了 v6 一時之間切換過來很不習慣。
不過 NestJS 也并不是沒有缺點。舉個例子,Node.js 通常使用 async hooks 進行異步資源跟蹤,比如日志。NestJS 的依賴注入機制提供了一種 Request 作用域的 Provider,表面上看完全可以解決請求上下文的資源共享,但實際上并不好用,因為 NestJS 對 Request 作用域的 Provider 有一條額外的限制:依賴 Request 作用域 Provider 的 Provider 也必須是 Request 作用域的(很拗口吧)。由于日志子產品是通用子產品,被很多子產品依賴,是以在這條限制下,從 app scope 到 module scope,幾乎每個 Provider 都會被牽涉。是以最後還是用了正常方案:cls-hooked。
也有可能是我學藝不精,還沒掌握 NestJS 的精髓,歡迎指正。
服務治理
Node.js 服務部署在公司的未來雲 k8s,上層沒有接網關,是以 https 的支援是在由 ingress 這一層提供,目前的方案比較原始,沒有自動化工具,需要手動修改 ingress 配置。除此之外,在服務治理方面需要重點關注兩個方面:守護程序和日志管理。
守護程序
在 k8s 普及之前,Node.js 的程序守護需要借助一些第三方工具,比較知名的比如 forever 和 pm2。使用這些工具會占用一些額外的機器資源(cpu、記憶體),借助 k8s 探針完全可以取代它們。
未來雲提供了兩種存活檢查的探針:Http 和 TCP。
Http 探針本質上是向某個接口發起 Get 請求,響應成功狀态碼代表服務健康,否則判定為壞死重新開機 pod。對于 Node.js 來說就相當于一次請求,是以需要 Node.js 提供一個專用的接口比如
/health
,需要額外工作,并且這個接口不應該記錄日志。
TCP 探針本質上是嘗試與容器建立 socket 連接配接,成功代表服務存活,否則判定為壞死重新開機 pod,對 Node.js 服務本身沒有依賴和影響。
Http 探針由于發起的是一個真實請求,是以通用性更強,但是需要額外的工作。
如果 Node.js 上層有額外的一層反向代理比如Nginx,那麼一定不要使用 TCP 探針。因為 Nginx 本身就能夠建立 TCP 連接配接,是以如果用 TCP 探針的話檢測結果永遠是健康的。
資料中台的 Node.js 服務每個 pod 都是單核,沒有起多程序,也就沒有使用反向代理的必要性,是以最終使用 TCP 探針做存活檢測。
日志管理
與 Java 服務的日志一樣,Node.js 服務的日志也是 ELK 一把梭。需要注意兩點:
-
與 Java 後端的日志串聯。
Node.js 與 Java 後端約定一個日志串聯的規範,Node.js 向 Java 發起的請求頭中攜帶一個額外字段
,值為 Node.js 生成的 requestId。x-trace-id
-
過期日志檔案及時清理。
Node.js 的日志檔案以天為機關分割檔案,每天都會建立幾個單獨的檔案(errors/warnsing/infos/expcetions),如果不及時清理的話會把磁盤打爆進而造成服務重新開機,是以需要添加一個定時任務清理過期檔案。
總結
現階段的 Node.js 中間層剛起步,還比較輕量,以後還會遇到更多挑戰。本文簡單記錄了一些搭建過程中的經驗,有正面的可能也有反面的,歡迎指導。
下一篇會寫一下目前接入的幾個項目中 Node.js 中間層扮演的角色和具體做的事情,敬請期待。