Node EE方案 -- Rockerjs在微店的建設與發展

royalrover發表於2019-05-09

本文是根據2019.4.13日參加 “Node-Party”論壇使用的PPT,加上筆者新的思考與沉澱而來。在此再次感謝貝貝網前端部門和芋頭君以及相關與會人員的支援! —— 微店楊力(曾用名 欲休)

  1. Node EE的前世今生
    • 什麼是 Node EE
    • Node EE的誕生
    • Node EE範疇
    • 總結
  2. Rockerjs的野蠻生長
    • 什麼是Rockerjs
    • Rockerjs-Core
    • Rockerjs-MVC
    • RPC
    • ORM
    • 分散式呼叫鏈路追蹤
      • 自動埋點
      • 埋點與“ThreadLocal”
    • APM
      • 監控
      • 診斷
    • 總結
  3. 未來的挑戰
  4. JOIN US,JOIN NODE EE GROUP

Node EE的前世今生

什麼是 Node EE

Node EE全稱為 “Node Enterprise Edition”,它是微店在探索Node.js在企業級開發過程中結合中介軟體、運維、測試相關經驗與方案,給出的一套相對完整的企業級解決方案。

我們可通過數學集合圖瞭解Node EE與Node.js的關係:
enter image description here

Node.js的集合部分包括:程式、檔案、網路、流、JavaScript語言,再往底層包括JavaScript引擎V8和事件迴圈的實現libuv,通常我們都是用Node.js的這些模組以及相關的底層服務實現業務邏輯。

Node EE集合包含了Node.js,那麼Node EE到底有哪些 “額外”的功能呢?在這裡先埋下一個疑點,我們將會在下文中給出答案。

哎等等,現在只介紹了Node EE大概是什麼,還未點 “前世今生” 的題呢。我們把思路撤回到原點,Node EE是如何誕生的,它是KPI的產物嗎,是重複造輪子嗎?

Node EE的誕生

首先扔出一張圖,
enter image description here
作為開發人員,我們非常熟悉當需求來時,每個角色的分工如何。在這裡總結了技術人員視角的三類角色:

  • 與使用者、產品距離最近,衝在使用者側第一線的“市場、產品與運營”
  • 設計與研發側,包括“UED、業務開發、測試”
  • 基礎側,包括“中介軟體、運維”

這三類角色完成了網際網路公司日常的運營生產活動。由市場人員洞悉當前需求或運營同學發起一些日常活動,由產品人員進行具體的需求總結與產品設計,這樣“任務流程”就流轉到第二層“設計研發側”。通過UED設計、視覺評審、prd評審、技術評審後開始進入具體業務開發,測試人員設計測試用例以及可能存在的效能測試、安全測試等;於此同時底層的“基礎側”需要給上層提供相關服務,如機器、儲存、CI、中介軟體產品、基礎介面等,保障研發側的順利上線。當研發側與基礎側都測試完畢後,交付給市場人員、運營和使用者,完成一輪生產流程。

這個生產流程每日在公司不停的上演,以致於在大多數參與其中的成員看來也沒什麼問題,都已習慣於這樣的生產模式中。可是在仔細分析整個生產流程中,我們會發現一個問題,一個有關 生產速率 的問題:

enter image description here
上圖中,黑色的虛線標識生產流程的流轉,右側的三種齒輪代表不同角色的響應速率。作為衝在第一線的“市場、產品與運營”而言,他們為了響應市場、使用者、輿論的反應,必須快速應對,因此對應紅色的齒輪必須高速率運轉;可作為“研發側”的“UED、業務開發和測試人員”而言,無法及時快速響應第一線的“小、快”需求,因此對應的黑色齒輪轉速遠低於紅色齒輪。研發側無法快速響應,不僅僅與提出的大量需求有關,也和研發的客觀規律有關,“快速”與“可靠”很難進行權衡;作為基礎側,由於相關係統建設且功能逐漸穩定,因此響應的速度也是較快,即藍色齒輪轉速高於上層的黑色齒輪。

簡單來說,生產活動中,紅色齒輪轉速過快,黑色齒輪轉速太慢,藍色齒輪轉速一般。這類似與“木桶理論”,由於瓶頸(研發側)的存在,導致整個流程無法快速執行,也是大多數企業面臨的頭痛問題。

換個角度,從經濟學中的“微笑曲線”同樣能找到相關情形:

enter image description here
“微笑曲線”的縱軸為“附加值”,可粗略理解為產品的增值,橫軸為產業鏈,對比上圖可帶入為三種角色。
其中微笑曲線產業鏈的“銷售、品牌營銷”對應網際網路中生產經營的第一層“市場、產品與運營”,產業鏈的“生產製造”對應於研發層“業務開發、測試”,產業鏈的“設計、研發”對應於研發層的“業務開發、測試”和基礎側的“中介軟體、運維”等。

在網際網路公司中,作為產業鏈附加價值最低的生產製造環節其實是“業務開發與測試”,這裡充斥了太多頻繁、瑣碎、重複且沒有太多技術含量的勞動量,而且由於生產製造環節流程冗長,因此佔用了較大的生產時間。但是,“業務開發與測試”同時也是產業鏈中“研發”的一部分,在疲於生產製造的同時還需要進行部分研發任務,如系統架構、高可用優化、資料採集分析、自動化測試等,這一項顯然大大提高了相關的附加值。

因此,根據“微笑曲線”,作為業務研發人員要想最大化自身價值,應該儘可能的將自己的產業鏈屬性向“研發側”延伸,盡一切努力擺脫千篇一律的“生產製造”屬性,同時通過某種手段,減少“生產製造”環節的時間佔比,甚至於在網際網路公司特殊的輕資產模型下,由產業鏈的另一端“銷售、營銷”實現快速“生產”。

為了解決效能問題並且兼顧提升業務開發人員的“設計、研發”能力,微店給出了 中臺化的答案,由研發側向“一線市場、產品與運營”交付各種系統,讓他們自己快速實現自己的需求並上線,如果無法實現,則由業務人員快速產出並沉澱為模組待下次使用。這就對 中臺化 的各種系統提出了強大能力的要求,目前,微店在 建模平臺、搭建平臺、資料分析平臺、前後端協作平臺、介面搭建平臺等領域都進行了嘗試,實現了超過60%的需求在第一線解決。

搭建這麼多服務平臺,離不開服務端編碼。我們經過調研了Java技術棧、Node.js技術棧和Golang技術棧,最終選擇了前端開發人員比較熟悉的Node.js技術棧,在這裡不詳細講述。因此,可以說是微店的中臺化催生了微店全棧化,而微店全棧化在發展過程中提出了Node EE理念。

enter image description here

總結一下,齒輪轉速均衡 -> 業務側研發快速響應 -> 業務建模平臺、搭建平臺等支援、專業的業務輔助團隊解決平臺無法解決的部分 -> 全棧化 -> Node EE ,這就是Node EE的產生。

Node EE範疇

還記得首節的Node EE與Node.js的數學集合圖嗎?它只表明兩者的包含關係並未細化Node EE的每一塊領域。下圖則是Node EE在Node.js基礎之上衍生出來的相關方向:
enter image description here

Node EE包括了 “應用容器、呼叫鏈路追蹤、RPC、模組擴充套件規範SPI、Starter機制、註解、除錯(遠端)、APM” 等方面,這是在微店的生產過程中總結出的可代表大多數場景的幾個方向,如有其他方面的遺漏,歡迎加入我們 Node EE小組 一起探討。

應用容器並不是一個新的概念,在Java領域早已成為一種規範。但是在Node.js領域沒有容器的說法,某些方面講程式管理工具如PM2在某些方面卻擁有容器的部分功能。Node EE中的應用容器可管理所有註解標識類的例項化物件,並管理其生命週期、物件間的依賴關係;當使用這些物件時可通過註解直接引用,無需手動例項化或建立物件間依賴;同時它也負責各種模組的初始化與執行,如Component、Filter、Controller和Server。因此,容器化可以讓開發者無需關心依賴、編寫可重構程式碼,把複雜的事情留給容器。

呼叫鏈路追蹤是後端開發中必須解決的問題,開發者和測試人員必須清楚每一個請求對應的後端鏈路,分析瓶頸並解決問題。

RPC則是構建微服務必不可少的一環,並且必須與 鏈路追蹤 打通才有意義,同時Node應用不應只作為RPC的消費者,有許多場景需要Node應用提供服務提供方的要求,因此也需要考慮。

關於SPI(Service Provider Interface),則是Node EE對面向擴充套件開放的一種實現規則。Starter機制是Node EE兼顧編碼理念 配置優先還是約定優先 的一種嘗試。

Node EE主張採用超程式設計的方法簡化程式碼邏輯,TypeScript的裝飾器是我們最終的選擇。在Node EE中將裝飾器稱之為Annotation(註解),它是Node EE建設的基石,貫穿於編碼的方方面面。通過Annotation實現DI(依賴注入)和元資訊註冊可極大簡化程式碼,理清邏輯。

至於debug和APM,則是應用正常執行的保障。遠端debug保障線上即時除錯,APM則時刻監視應用資源的狀況,同時提供線上profile功能。

總結

Node EE是面向企業級應用開發場景,滿足應用高可維護、可擴充套件,在無縫接入各級中介軟體同時,能追蹤請求的各層鏈路、遠端除錯、線上實時監控與效能分析。

Node EE有以下特點:

開發時體驗爽

執行時放心跑

故障時快速調

重構時儘管改

哎呦
enter image description here

“做企業和開發者喜歡的Node EE方案” -- Rockerjs的目標

Rockerjs的野蠻生長

什麼是Rockerjs

Rockerjs是微店對Node EE的一種探索和實現。它基於註解提供 IoC 和 AOP 的特性在簡化模組依賴的同時讓編碼二維化,基於此衍生出來了MVC框架、RPC、Node Persistence of XML、ThreadLocal 、trace、SPI、分散式事務及容器監控等中介軟體,目前微店內部多個平臺與外網服務基於此而生。

Rockerjs的核心理念是: 容器化與IoC。容器化可讓開發人員專注於業務,不關心非核心業務的實現;IoC則簡化依賴,依賴倒置,可擴充套件性高。

Rockerjs-Core

Rockerjs的核心是 Rockerjs-Core,它是基於 TypeScript 和註解的輕量級IoC容器,提供了依賴注入、面向切面程式設計及異常處理等功能。Rockerjs-Core可在任意工程中引入,是一個框架無關的IoC容器。原始碼:https://github.com/weidian-inc/rockerjs-core 文件:https://rockerjs.weidian.com/rockerjs/core.html

Rockerjs-MVC

Rockerjs的主要應用場景是Web服務端開發, Rockerjs-MVC 便是為了解決服務端開發的MVC框架。它基於 Rockerjs-Core構建,是一套基於配置、具有輕量級容器特性且整合了鏈路追蹤功能的Node.js Web應用框架。 原始碼:https://github.com/weidian-inc/rockerjs-mvc 文件:https://rockerjs.weidian.com/compass/mvc.html

Rockerjs-MVC有如下特點:

  • 配置大於一切
  • 約定簡化編碼
  • 超程式設計思想
  • DI解耦
  • 預設整合呼叫鏈路追蹤
  • TS強型別約束
  • 物件導向、面向介面
  • 熟悉的main函式

技術細節如下:
enter image description here
由容器維護Rockerjs-MVC和應用中的各種模組,如component、starter、filter、controller等。filter採用職責鏈模式可通過配置檔案設定順序,最終請求由 dispatcher 分發給對應的controller。service負責每次處理事務,包括DAO、RPC等。最終controller的響應再由dispatcher下發給外掛 view resolver,渲染完畢後返回響應。
Rockerjs-MVC內部通過外掛的形式擴充套件渲染模板,目前提供了基於vue和ejs模板的渲染引擎。

Rockerjs-MVC例項

index.ts

import { Application, AbstractApplication } from "@rockerjs/mvc";

@Application
class App extends AbstractApplication{  
  public static async main(args: RockerConfig.Application) {
    console.log('main bussiness', args);
  }
}

homeController.ts

import { Controller, Get, Param, Request } from  '@rockerjs/mvc';

@Controller("/home")
export class HomeController {
  @Get({url: '/a'})
  async  home(@Param("name") name: string, @Param("person") person: object) {
    return {
    tag: 'hello world',
    name,
    person
    }
  }
}

app.dev.config

port=8080

[filter:trace]

[mysql]
starter=@rockerjs/mysql-starter
host=127.0.0.1
user=NODE_PERF_APP_user
port=3308
database=NODE_PERF_APP
password=root
resourcePath=model/resource

Rockerjs-MVC中通過 app.${env}.config 定義相關初始化資訊,相關的類與中介軟體都交於容器並根據配置檔案進行例項化和初始化,這樣就完成一個最簡單應用的搭建。具體使用,請詳見 文件

RPC

Node EE推薦的RPC方式為 “Dubbo和HTTP”。Node EE設計初期考慮到與現存系統無縫接入的需求,因此毫無顧慮的投入 Dubbo 的懷抱。在Dubbo中介軟體領域內,我們開發了基於此協議的 ConsumerProvider支撐業務需求。

  • consumer
    1. 支援泛化呼叫,無需宣告介面動態呼叫
    2. 測試階段可配置負載策略
  • provider
    1. 支援泛化呼叫
    2. 相容常規HTTP介面
    3. Java研發快速上手
    4. 服務治理

關於Dubbo Provider,我們採用Proxy和Facade設計模式儘可能讓前後端開發人員快速、可擴充套件的編寫程式碼,同時相容已有專案。

@Dubbo({
  interface: 'com.vdian.vstudio.service.ProjectService',
  method: 'getProjectInfo',
  params: ['id','type'],
  version: '1.0.0',
  nodeServerUri: '/dubbo/get/project'
})
@Post({ url: '/get/project'})
@AutoWrap
public async getProjectInfo(@Param('param') param: {id: number, type: string}, @Param('context') context: object) {
  const {id,type} = param;
  let result = await this.projectService.getProjectInfoAll(id,type);
  return {
    project: result
  }
}

技術細節圖如下:
enter image description here
關於Dubbo Provider的詳細細節,可參考我的一篇文章# [Nodejs“實現”Dubbo Provider](https://www.cnblogs.com/accordion/p/9391320.html)

ORM

ORM的框架與庫有很多,可是在開發過程中傳統的基於物件操作實現SQL的生成往往會有些問題:

  1. 複雜查詢如join的支援、多表查詢
  2. 效能
  3. 程式碼維護差
  4. 安全審計無從談起

因此Node EE並沒有採用傳統的ORM框架,而是採用XML模板渲染的形式構建SQL語句,語法與mybatis極度相似。這樣的好處不言而喻:

  1. 模板重用性極高
  2. 方便維護
  3. 後端人員無縫上手
  4. 安全審計容易

示例:
appInfoMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE  mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper  namespace="appInfo">
  <select  id="queryAll"  resultType="../do/App_Info">
    select * from app_info order by gmt_create desc
  </select>
  
  <insert  id="add">
    insert into app_info ( appid,secrete,username,appname )
    <trim  prefix="values ("  suffix=")"  suffixOverrides=",">
      #{appid},#{secrete},#{username},#{appname}
    </trim>
  </insert>

  <update  id="editBatchWithCondition">
    update app_info
    <set>
        <foreach  collection="Object.keys(data)"  item="key"  index="index" >
            <if  test="index <= (Object.keys(data).length -2)">
                ${key} = #{data[key]},
            </if>
            <if  test="index > (Object.keys(data).length -2)">
                ${key} = #{data[key]}
            </if>
        </foreach>
    </set>
    <where> 
        <foreach  collection="Object.keys(info)"  item="key"  index="j" >
            <if  test="j <= (Object.keys(info).length -2)">
                ${key} = #{info[key]} and
            </if>
            <if  test="j > (Object.keys(info).length -2)">
                ${key} = #{info[key]}
            </if>
        </foreach>
    </where>
  </update>

  <delete  id="del">
    delete from app_info where appid=#{appid}
  </delete>
</mapper>

詳細使用場景,可參考示例:https://github.com/weidian-inc/rockerjs-demo

分散式呼叫鏈路追蹤

當開啟微店商品詳情頁時,該請求在後端所有鏈路的追蹤如下所示:
enter image description here

每個請求處理都可追溯到每次RPC呼叫、每次中介軟體呼叫,呼叫結果及響應時間都可追溯到。這樣在消耗一些效能的前提下完成所有請求的追蹤是價效比極高的行為,在請求出錯的排查、鏈路壓測等情況下尤其有用。

那麼,Node.js中如何實現鏈路追蹤的呢?這得益於 Rockerjs-MVC 提供的tracer機制以及相關生態 “Rockerjs-midLogger-starter、Rockerjs-mysql-starter、Rockerjs-redis-starter、Rockerjs-RPC-starter”的支援

enter image description here
當請求過來時,由閘道器層生成一個全域性唯一的TraceId,在所有的系統呼叫中傳遞,包括RPC、DB、Redis、MQ。同時通過日誌採集儲存在不同的儲存介質中,進行離線或實時分析,最終通過看板進行呈現或設定。

那麼,Node EE如何進行呼叫資訊的傳遞呢?

自動埋點

  • 由 Rockerjs-MVC和其他中介軟體建立呼叫上下文,生成埋點資訊
    • TraceId、RPCId、isSample等
  • 自動埋點,埋點資訊由中介軟體自動放入當前請求的“ThreadLocal”,對開發者完全透明
  • 呼叫上下文在整個鏈路的透傳
    • Dubbo呼叫採用Attachment機制
    • HTTP採用header透傳
    • 中介軟體請求則本地記錄日誌

埋點與“ThreadLocal”

我“自作主張”在Node.js領域起了一個已存在的名詞 “ThreadLocal”,它實際上是不準確的,因為Node.js中執行執行緒只有一個不存在多個執行執行緒,不過為了大多數人的直觀理解,本文仍然採用“ThreadLocal”。準確的講,它應該被叫做 “Async Context Bound”,即非同步上下文繫結。它可與請求相繫結,在HTTP上下文、WebSocket上下文、中介軟體上下文都可使用。

ThreadLocal 變數作為執行緒內的區域性變數,在多執行緒下可以保持獨立,它存在於執行緒的生命週期內,可以線上程執行階段多個模組間共享資料。

上節中的鏈路追蹤就是採用“ThreadLocal”特性實現的,它可脫離HTTP上下文在任意場景下獲取相關資訊。

關於“ThreadLocal”的實現,可參考我的兩篇文章:

  1. 基於Zone.js的實現:node.js與ThreadLocal(AsyncContext Bound)
  2. 基於Async Hooks的實現:https://github.com/royalrover/threadlocal

APM

關於應用效能監控與除錯,由於有了alinode和easy-monitor的存在,在這裡不再詳細贅述(具體的實現大體一致)。我們自建了NPS效能平臺,專注於Node應用效能監控與線上Profile分析:
enter image description here
通過看板可選擇Node專案相關操作,如診斷與監控。

監控

監控主要從三個維度進行,分別是“堆記憶體、CPU使用率和GC頻率”,基本可表徵應用的當前執行資訊與資源瓶頸:
enter image description here
enter image description here
enter image description here

診斷

診斷部分參考了easy-monitor的UI設計,在此感謝作者。NPS可針對程式的記憶體與CPU進行打點分析,如:
enter image description here
同時和easy-monitor一樣提供了自動分析功能:
enter image description here
enter image description here

通過實時監控和線上診斷,再配合遠端debug,可以放心的線上上執行Node應用。

總結

梳理了Node EE的各個方面,整理了一張結構圖,如下:
enter image description here

自底向上,所有的應用程式的效能資訊、線上分析功能都依託於 “probe agent” 服務,它以deamon的形式存在於每一臺機器上;在此之上,每個應用的核心都是容器,由它負責依賴管理、例項建立、模組初始化、應用啟動;在容器之上,衍生了Rockerjs-MVC,同時提供特殊的異常處理機制、SPI擴充套件規範、AOP程式設計以及基於註解的tsunit;在右側有顏色部分,trace追蹤散佈於呼叫各階段;監控、遠端除錯和日誌同樣貫穿於應用的整個生命週期。

Node EE中還有些細節並沒有探討,比如程式管理、遠端除錯等等,這會在後續提供相應服務。

未來的挑戰

Node EE是微店在自己業務範圍內探索的一套適合自己 小步快跑、快速迭代 業務特點的解決方案,它不可能涵蓋所有的場景和需求,因此有些遺漏實屬正常,需要社群一起共建。

目前,仍然存在三個方向急需建設:

  1. 生態建設
    • WebSocket相容
    • xxxStarter
    • CLI
    • Plugins(graphQL、Restful)
    • ...
  2. 框架周邊建設
    • Rockerjs-MVC
    • Rockerjs-DAO
    • Rockerjs-Tracer
    • Rockerjs-TCC
  3. 文件建設

JOIN US,JOIN NODE EE GROUP

enter image description here
邀請碼若失效,請聯絡筆者微信 royalrover,萬分感謝閱讀!!!

相關文章