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

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

  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在企業級開發過程當中結合中間件、運維、測試相關經驗與方案,給出的一套相對完整的企業級解決方案。html

咱們可經過數學集合圖瞭解Node EE與Node.js的關係: 前端

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940138b8f63?w=357&h=235&f=png&s=19820)

Node.js的集合部分包括:進程、文件、網絡、流、JavaScript語言,再往底層包括JavaScript引擎V8和事件循環的實現libuv,一般咱們都是用Node.js的這些模塊以及相關的底層服務實現業務邏輯。vue

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

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

Node EE的誕生

首先扔出一張圖, git

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a9400edf24cb?w=335&h=252&f=png&s=21443)
做爲開發人員,咱們很是熟悉當需求來時,每一個角色的分工如何。在這裏總結了技術人員視角的三類角色:

  • 與用戶、產品距離最近,衝在用戶側第一線的「市場、產品與運營」
  • 設計與研發側,包括「UED、業務開發、測試」
  • 基礎側,包括「中間件、運維」

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

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

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a9401d4e67e4?w=411&h=279&f=png&s=54454)
上圖中,黑色的虛線標識生產流程的流轉,右側的三種齒輪表明不一樣角色的響應速率。做爲衝在第一線的「市場、產品與運營」而言,他們爲了響應市場、用戶、輿論的反應,必須快速應對,所以對應紅色的齒輪必須高速率運轉;可做爲「研發側」的「UED、業務開發和測試人員」而言,沒法及時快速響應第一線的「小、快」需求,所以對應的黑色齒輪轉速遠低於紅色齒輪。研發側沒法快速響應,不只僅與提出的大量需求有關,也和研發的客觀規律有關,「快速」與「可靠」很難進行權衡;做爲基礎側,因爲相關係統建設且功能逐漸穩定,所以響應的速度也是較快,即藍色齒輪轉速高於上層的黑色齒輪。

簡單來講,生產活動中,紅色齒輪轉速過快,黑色齒輪轉速太慢,藍色齒輪轉速通常。這相似與「木桶理論」,因爲瓶頸(研發側)的存在,致使整個流程沒法快速運行,也是大多數企業面臨的頭痛問題。sql

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

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a94018e143c9?w=1148&h=652&f=png&s=39308)
「微笑曲線」的縱軸爲「附加值」,可粗略理解爲產品的增值,橫軸爲產業鏈,對比上圖可帶入爲三種角色。 其中微笑曲線產業鏈的「銷售、品牌營銷」對應互聯網中生產經營的第一層「市場、產品與運營」,產業鏈的「生產製造」對應於研發層「業務開發、測試」,產業鏈的「設計、研發」對應於研發層的「業務開發、測試」和基礎側的「中間件、運維」等。

在互聯網公司中,做爲產業鏈附加價值最低的生產製造環節實際上是「業務開發與測試」,這裏充斥了太多頻繁、瑣碎、重複且沒有太多技術含量的勞動量,並且因爲生產製造環節流程冗長,所以佔用了較大的生產時間。可是,「業務開發與測試」同時也是產業鏈中「研發」的一部分,在疲於生產製造的同時還須要進行部分研發任務,如系統架構、高可用優化、數據採集分析、自動化測試等,這一項顯然大大提升了相關的附加值。

所以,根據「微笑曲線」,做爲業務研發人員要想最大化自身價值,應該儘量的將本身的產業鏈屬性向「研發側」延伸,盡一切努力擺脫千篇一概的「生產製造」屬性,同時經過某種手段,減小「生產製造」環節的時間佔比,甚至於在互聯網公司特殊的輕資產模型下,由產業鏈的另外一端「銷售、營銷」實現快速「生產」。

爲了解決效能問題而且兼顧提高業務開發人員的「設計、研發」能力,微店給出了 中臺化的答案,由研發側向「一線市場、產品與運營」交付各類系統,讓他們本身快速實現本身的需求並上線,若是沒法實現,則由業務人員快速產出並沉澱爲模塊待下次使用。這就對 中臺化 的各類系統提出了強大能力的要求,目前,微店在 建模平臺、搭建平臺、數據分析平臺、先後端協做平臺、接口搭建平臺等領域都進行了嘗試,實現了超過60%的需求在第一線解決。

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

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a9400f797eb8?w=304&h=356&f=png&s=10966)

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

Node EE範疇

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

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a9400f6f7b55?w=501&h=296&f=png&s=30362)

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](https://user-gold-cdn.xitu.io/2019/5/9/16a9a9404cf96e2f?w=522&h=309&f=png&s=107592)

「作企業和開發者喜歡的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容器。源碼:github.com/weidian-inc… 文檔:rockerjs.weidian.com/rockerjs/co…

Rockerjs-MVC

Rockerjs的主要應用場景是Web服務端開發, Rockerjs-MVC 即是爲了解決服務端開發的MVC框架。它基於 Rockerjs-Core構建,是一套基於配置、具備輕量級容器特性且集成了鏈路追蹤功能的Node.js Web應用框架。 源碼:github.com/weidian-inc… 文檔:rockerjs.weidian.com/compass/mvc…

Rockerjs-MVC有以下特色:

  • 配置大於一切
  • 約定簡化編碼
  • 元編程思想
  • DI解耦
  • 默認集成調用鏈路追蹤
  • TS強類型約束
  • 面向對象、面向接口
  • 熟悉的main函數

技術細節以下:

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940b34d25a1?w=558&h=421&f=png&s=21015)
由容器維護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](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940b37e3b8d?w=676&h=335&f=png&s=44455)
關於Dubbo Provider的詳細細節,可參考個人一篇文章[# Nodejs「實現」Dubbo Provider]( www.cnblogs.com/accordion/p…)。

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>
複製代碼

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

分佈式調用鏈路追蹤

當打開微店商品詳情頁時,該請求在後端全部鏈路的追蹤以下所示:

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940b38e5c90?w=960&h=451&f=png&s=325565)

每一個請求處理均可追溯到每次RPC調用、每次中間件調用,調用結果及響應時間均可追溯到。這樣在消耗一些性能的前提下完成全部請求的追蹤是性價比極高的行爲,在請求出錯的排查、鏈路壓測等狀況下尤爲有用。

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

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940b581cab4?w=904&h=409&f=png&s=33874)
當請求過來時,由網關層生成一個全局惟一的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的實現:github.com/royalrover/…

APM

關於應用性能監控與調試,因爲有了alinode和easy-monitor的存在,在這裏再也不詳細贅述(具體的實現大致一致)。咱們自建了NPS性能平臺,專一於Node應用性能監控與在線Profile分析:

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940ce81a5a4?w=960&h=406&f=png&s=71901)
經過看板可選擇Node項目相關操做,如診斷與監控。

監控

監控主要從三個維度進行,分別是「堆內存、CPU使用率和GC頻率」,基本可表徵應用的當前運行信息與資源瓶頸:

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940d243d6d5?w=960&h=335&f=png&s=137349)
![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940d232a8db?w=960&h=298&f=png&s=78290)
![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940d6f2e0f2?w=960&h=294&f=png&s=129190)

診斷

診斷部分參考了easy-monitor的UI設計,在此感謝做者。NPS可針對進程的內存與CPU進行打點分析,如:

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940d349483d?w=960&h=389&f=png&s=63216)
同時和easy-monitor同樣提供了自動分析功能:
![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940ef159390?w=859&h=413&f=png&s=28026)
![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940f04c8576?w=449&h=492&f=png&s=34527)

經過實時監控和線上診斷,再配合遠程debug,能夠放心的在線上運行Node應用。

總結

梳理了Node EE的各個方面,整理了一張結構圖,以下:

![enter image description here](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940f8998266?w=790&h=475&f=png&s=59543)

自底向上,全部的應用進程的性能信息、在線分析功能都依託於 「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](https://user-gold-cdn.xitu.io/2019/5/9/16a9a940f8a578cc?w=246&h=234&f=png&s=13193)
邀請碼若失效,請聯繫筆者微信 royalrover,萬分感謝閱讀!!!
相關文章
相關標籤/搜索