Node.js 在微醫的應用場景及實踐高翔

微醫前端團隊  方凳雅集轉載


我是來自微醫集團消費事業羣的前端工程師高翔,這篇文章整理自我在《第一屆繽紛前端技術沙龍》的主題分享《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 數據快照:



在客戶端入口文件(entry-client.js)中,咱們需求經過 window.__INITIAL_STATE__ 變量來判斷本次渲染是正常渲染仍是降級渲染,若是是降級渲染,還須要手動的調用 asyncData 方法進行首屏的數據預取。


至此,降級的入口文件就完成了,接下來就是在 SSR 的流程中,咱們在什麼階段將這個 SPA 文件返回給瀏覽器。


這邊有一個流程圖,當請求進來時,判斷 URL 中是否有 Degrade 參數或者配置文件中有沒有開啓全局降級,若是有則進行降級,若是沒有再看服務端渲染流程中有沒有發生異常,發生異常也會進行降級。另外還要區分開發環境和正式環境對 SPA 應用的獲取規則:開發環境下從內存中取,正式環境從磁盤中取,由於開發環境下用了 webpack-dev-middleware,是構建到內存中的。

到這裏,整個降級機制的實現原理就介紹完了,有了這個機制,咱們能夠很是方便的讓應用隨時降級成單頁,來觀察接口的調用(開發、測試利器)。並且,若是服務端渲染出現異常,用戶也不會看到一個冷冰冰的 500 頁面了。另外,對於一些大流量的場景,也能夠經過配置文件開啓全局降級,讓 SSR 應用直接變成一個 SPA 應用,減小服務器的負擔。

04

服務端緩存


對於一些與用戶無關的接口和頁面,就是說不論是誰訪問返回的結果都是相同的,對於這些接口咱們可使用 LRU-Cache (最近最少使用)作服務端緩存,更好的下降內容到達時間。

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

內部框架封裝


Aug.js 是一個基於 Egg.js 基礎框架,使用 TypeScript 開發的 Node.js 應用框架,集成了 Log、APM、Gtrace 以及 Eureka 等基礎中間件,致力於打造通用的企業級 Node 應用解決方案。


Node.js 生態建設



其實咱們在團隊內 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 應用的容錯降級、全鏈路追蹤仍是性能監控,能夠看到,對於線上應用來講穩定性保障設施是必不可少的。


最後,重要的話要說三遍:不要重複造輪子!不要重複造輪子!不要重複造輪子!

相關文章
相關標籤/搜索