我是來自微醫集團消費事業羣的前端工程師高翔,這篇文章整理自我在《第一屆繽紛前端技術沙龍》的主題分享《Node.js 在醫療行業的應用》,介紹了 Node.js 在微醫的發展歷程和應用實踐經驗。
前端
微醫是總部位於杭州蕭山的一家互聯網醫療公司,咱們的前端研發人員從2015年的幾我的發展到如今的120多人,前端技術棧體系發生了巨大的變化,下面這張圖展現了咱們部門前端團隊的技術棧演進過程。webpack
16年以前主要是先後端耦合的開發方式。
git
17年開始引進 Vue,進行先後端分離,並開始嘗試作 Vue SSR 的探索。github
18年全面推 Vue SSR,積累了必定的 Node.js 經驗,出現了愈來愈多的線上 Node.js 應用。web
今年主要是將以前的解決方案沉澱下來,變成框架、文檔、插件、腳手架等,來更好的支持需求的迭代。數據庫
能夠看到在微醫, Node.js 在線上應用起步較晚,可是發展很快,例如消費線業務基本都遷移到了 SSR 技術體系。下面這張圖是咱們公司前端應用的分佈狀況。編程
能夠看到,集團整體前端應用中,Node.js 應用大概佔比 1/4,而在 Node.js 應用中主要是 SSR 應用,其次是一些全棧體系的內部應用,接着是一些 API 服務,作一些接口的聚合和轉發。
json
因此我今天主要從 Vue SSR、內部應用和 API 服務來介紹一下 Node.js 在微醫的使用狀況。後端
應用場景一:內部工具瀏覽器
咱們團隊在業務之餘作了不少內部效率工具,來服務於集團內的其餘角色,包括開發、測試、運營、產品等等,這些應用都有一個特色:使用 Node.js 來進行後端服務層的開發。這邊我列舉了三個咱們的內部應用:
筋斗雲 - 需求發佈管理系統
魔方 - 可視化活動頁面搭建平臺
智能運營管家 - 社羣運營工具
其中,筋斗雲系統用於支撐需求的發佈管理,是使用頻率最高的內部平臺之一。
咱們以需求爲單位,將整個需求發佈流程中一些須要人工操做和不嚴謹的點整理出來,作成一個平臺,來進行發佈流程的把控,提升需求發佈的效率和質量,其中的一些功能,好比:經過 GitLab API 來作分支智能合併、發佈計劃管理、一鍵發佈、前端應用 package.json 變更監聽、release 分支落後檢測......這些小功能均可覺得開發和測試提升成倍的效率和質量。這個系統裏面 Node.js 也作了很是多的工做,好比數據的持久化、API 設計、Dubbo 服務調用 、釘釘消息推送、郵件提醒以及定時任務等等。
有了 Node.js 的加持,前端工程師可以發揮本身豐富的想象力,將想法變成產品,創造一些可以提升數倍效率的工具。
應用場景二:Vue 服務端渲染
除了內部應用,咱們在 Vue SSR 方面也作了不少的研究和實踐,先後端同構(同一種編程語言,同一套開發模式) 的應用擁有單頁的用戶體驗,又天然支持 SEO, 讓後端同窗從表現層解脫出來,專一服務開發。
在 Vue SSR 中,咱們的 Node.js 主要作了如下工做:
01
異步數據混合
咱們參考 Nuxt.js 中對 asyncData 的操做,在 asyncData 鉤子中獲取數據後直接將數據對象返回,而後與組件中的 data 函數的對象進行混合,頁面組件能夠像操做 data 同樣操做 asyncData 返回的數據,這樣就能夠很好的與 Vuex 解耦(官方推薦的作法是將 asyncData 中獲取的數據存到 Vuex 中,組件從 Vuex 中取數渲染)。
02
通用工具封裝
咱們作了一些通用方法的封裝,好比:Cookie操做、路由操做等等,抹平了服務端和客戶端的差別,同時還輸出了一些很是好用的插件。
feb-alive 是一個 Vue 頁面級緩存插件,使用 feb-alive 來代替 keep-alive 進行頁面緩存,可使咱們的應用達到瀏覽器級別的路由體驗(前進刷新,後退緩存),詳細使用能夠參考 https://github.com/wefront/feb-alive。
03
異常降級機制
對於通常的 SSR 應用來講,在服務端發生異常時都會返回一個 500 的頁面,其實對於某一些場景,好比:接口調用異常、asyncData 中寫了瀏覽器的代碼,對於這種狀況的異常咱們能夠返回一個 SPA 應用,在用戶的瀏覽器上進行渲染,達到高可用。在介紹降級機制以前,先來看降低級的效果。
一開始是正常的服務端渲染 ,接着在 URL 中帶上一個 degrade 參數後,服務端會返回一個 SPA 應用。而後我模擬了 asyncData 中接口異常的狀況,能夠發現服務端也返回了一個 SPA 應用,而不是一個 500 的異常頁面。
接着介紹一下 SSR 降級機制的實現原理。首先若是要實現這樣一套降級機制,咱們得在構建客戶端代碼的時候生成一個 SPA 應用:
瞭解 Vue SSR 的同窗都知道,服務端渲染會輸入一個 window.__INITIAL_STATE__ 變量,裏面存放着服務端的 Vuex 數據快照:
04
服務端緩存
05
接入全鏈路追蹤
咱們將 SSR 應用接入到內部的全鏈路追蹤系統中,在現代的微服務架構中,應用的調用鏈路是很是複雜的,而 Vue SSR 應用又是在調用鏈的最頂端,因此將 Node.js 接入這套全鏈路系統是很是有必要的。
06
內部框架沉澱
咱們將這套 SSR 解決方案封裝成一個內部框架、來更好的支持需求的迭代。出於框架可維護性、可升級性的考慮,咱們將 Webpack 和 Server 部分都內置了進去,但同時也暴露了方法讓用戶作一些自定義的操做(webpack-chain、middleware等)。
應用場景三:API 服務
BFF 的概念在國內是阿里最早提出來的,主要是前端工程師維護的在端應用和底層服務中間的一層,用來針對不一樣的用戶設備輸出不一樣的接口,作一些數據的聚合、裁剪、適配等。在微醫咱們主要作了一些底層服務的調用,進行接口的聚合和轉發,即 Node.js API 服務層。
上面這張圖展現了 Node.js 在整個微服務架構中的位置,最上層的是一些端應用,當一個請求發起時首先會通過 Java 的網關,網關做爲微服務的惟一入口,進行請求的分發和通用邏輯的處理,網關下面是一些 API 服務,用來提供 HTTP 接口,最底層是更加原子化、細粒度的 Dubbo 服務。Eureka 是網關和 API 層的註冊中心,Zookeeper 是 Dubbo 層的註冊中心。Node.js 在 API 層主要作了如下工做。
01
Dubbo 服務調用
咱們使用了社區提供的 dubbo2.js 進行 Dubbo 服務的調用,而且作了一些封裝來優化發開體驗。
固然,官方還推薦 jar-to-ts 的方案,將 dubbo 服務的 JAR 包轉成 TS 定義,來進行更方便的調用。
02
接入 Eureka 註冊中心
若是想接入統一的 API 網關,首先得將咱們的應用接入到 Eureka 註冊中心中,使用社區提供的 eureka-js-client 這個庫能夠很是快速的進行 Eureka 的接入。
03
裝飾器路由封裝
咱們的應用是基於 Egg.js 和 Typescript 的,可是咱們發現 Egg.js 的映射式路由對開發體驗有點不友好,因此咱們作了裝飾器路由的封裝。
04
內部框架封裝
Node.js 生態建設
01
全鏈路追蹤
爲何須要全鏈路追蹤系統
在現代化的微服務系統中,客戶端的一次請求操做,可能須要通過系統中多個模塊,如何肯定客戶端的一次操做背後調用了哪些應用,通過了哪些節點,前後順序是怎樣的?當出現 BUG 時,如何快速的定位問題出如今哪一環?還有每一個應用的性能如何,耗時有多長,咱們如何快速定位應用的瓶頸所在?
全鏈路追蹤系統的原理
當一條請求進來的時候,會生成一個惟一的 TraceId,來標記一條鏈路,每當進行後續的調用時,都會將 TraceId 傳下去,來串連起來整個鏈路。
Span 是一個上報單元,是一個原子化的操做,一個程序塊的調用,或者一次RPC/數據庫訪問均可以認爲是一個 Span,每一個 Span 都遵循一個固定的數據結構。
經過 TraceId 咱們能夠把每一次請求通過的系統收集起來:
每一層把 TraceId 傳遞下去(Http Header、Dubbo Attchment)。
每一層把 TraceId 上報上去。
經過 SpanId 和 ParentId 咱們能夠構建樹的父子關係:
每一層把 SpanId 傳遞下去。
每一層在構建 Span 時,若是在請求頭中有 SpanId 帶過來,則標記 ParentId 爲父級的 SpanId。
const spanId = generateUUID16()
const spanCtx = { spanId }
const parentSpanId = req.headers['span-id']
const parentTraceId = = req.headers['trace-id']
if (parentSpanId && parentTraceId) {
spanCtx.traceId = parentTraceId
spanCtx.parentId = parentSpanId
spanCtx.reference = reference.CHILD_OF
} else {
const traceId = generateUUID32()
spanCtx.traceId = traceId
spanCtx.parentId = traceId.substr(-16)
}複製代碼
複製代碼
經過 startTime 能夠肯定同級的前後順序,startTime 標記生成這條 Span 的時間,同級節點 startTime 排序後的順序就是節點展現的順序。
Node.js 和 Java 層面都是經過 Kafka 生產者將數據上報到消息隊列裏面。客戶端須要作特殊的處理,由於客戶端的應用協議比較單一,通常是 HTTP,因此不支持消息隊列上報,須要加一層代理層。ES 負責消費數據,進行鏈路的分析,最後由 Web 平臺進行鏈路的展現。
其實除了最基本的鏈路分析,這套系統還能夠作:慢調用分析、冷熱應用、相互依賴、日誌平臺整合、監控中心整合等等。
上面這張圖是 Node.js 應用接入到這套鏈路追蹤系統後的鏈路圖,能夠很是清晰的看到在服務端調用了哪些接口,甚至配合 Browser 端的 SDK 能夠將 SSR 應用的鏈路連接起來,進行用戶頁面訪問路徑、內容到達時間、頁面性能的分析。
02
性能監控平臺
咱們對比了經常使用的 APM 產品,從代碼侵入、實現方式、成熟度、數據安全性以及性能等各個方面進行對比。
咱們想要的是一個能夠在公司內部部署、侵入低、基礎監控完備的 APM 產品,最終咱們選擇了 Elastic APM 方案。
這套性能監控方案能夠知足咱們目前的需求,若是須要更深刻監控及分析,還能夠進行二次開發。
總結
今天給你們介紹了 Node.js 在微醫的應用探索及沉澱的狀況,能夠看到:
Node.js 是一個效率利器,有了 Node.js 的幫助,前端工程師能夠好的將想法變成產品。
不論是 SSR 應用的容錯降級、全鏈路追蹤仍是性能監控,能夠看到,對於線上應用來講穩定性保障設施是必不可少的。
最後,重要的話要說三遍:不要重複造輪子!不要重複造輪子!不要重複造輪子!