tars-node:RPC框架分析(node-agent, deploy, rpc, monitor, stream(部分))

總體架構(gòu):

node-agent

TARS框架中Node.js程序啟動(dòng)器,提供生產(chǎn)環(huán)境所需的服務(wù)屬性。

deploy

TARS框架服務(wù)打包工具

rpc

TARS框架中RPC通信模塊

monitor

TARS框架中用于服務(wù)監(jiān)控,特性監(jiān)控上報(bào)。

stream

TARS框架的編解碼工具

registry

TARS框架中的主控請(qǐng)求模塊

utils

TARS框架輔助工具集合

winston-tars

基于winston的擴(kuò)展,以提供符合TARS框架的日志格式與輸出

notify

TARS框架中用于業(yè)務(wù)的(告警)消息上報(bào)

logs

TARS框架規(guī)范的日志組件,包括滾動(dòng)(大小、時(shí)間)與染色日志

config

TARS框架中用于獲取服務(wù)配置文件

dyeing

TARS染色基礎(chǔ)模塊

node-agent

@tars/node-agent

為了讓Node.js應(yīng)用于TARS框架中,node-agent將作為啟動(dòng)器來啟動(dòng)應(yīng)用,提供生產(chǎn)環(huán)境所需的服務(wù)特性。它主要提供了如下的功能:

  • 內(nèi)置負(fù)載均衡(通過Cluster模塊實(shí)現(xiàn))
  • 異常退出的監(jiān)控與拉起
  • 日志搜索與處理
  • 支持TARS平臺(tái)的管理命令
  • 支持HTTP(s)服務(wù)監(jiān)控上報(bào)(在TARS平臺(tái)上運(yùn)行)
  • 支持服務(wù)用量上報(bào)(在TARS平臺(tái)上運(yùn)行)
安裝
npm install @tar/node-agent -g

由于node-agent是一個(gè)CLI程序,所以一般需要用-g參數(shù)來安裝

用法
node-agent app.js [options]
  • app.js為程序的入口腳本
  • [options]可選配置
例子

執(zhí)行app.js文件:

$ node-agent app.js

以TARS服務(wù)的配置文件來啟動(dòng):

$ node-agent app.js --config MTT.Test.conf

啟動(dòng)并命名應(yīng)用為MTT.Test:

$ node-agent app.js --name MTT.Test

定義日志輸出路徑:

$ node-agent app.js --log ./logs/

傳遞子進(jìn)程node的啟動(dòng)參數(shù):

$ node-agent app.js --node-args="--debug=7001"

定義子進(jìn)程數(shù)量:

$ node-agent app.js -i 4
入口點(diǎn)

node-agent啟動(dòng)時(shí)傳入的第二個(gè)參數(shù)用來制定服務(wù)腳本執(zhí)行的入口文件,其中:

  • 可以直接傳入腳本文件用于執(zhí)行,如:./app,js
  • 也可以傳入腳本文件所在的目錄,如:./
    當(dāng)傳入的為目錄時(shí),入口點(diǎn)根據(jù)如下順序進(jìn)行確認(rèn):
  1. 目錄中存在package.json文件,則:
    i. 查找 nodeAgent.main
    ii. 查找 script.start(此配置節(jié)需要以node打頭才可以識(shí)別)
    iii. 查找 main
  2. 查找目錄中是否存在: server.js、app.js、start.js、index.js,只要其中的一項(xiàng)匹配則作為入口文件來執(zhí)行,并不再往下匹配。
選項(xiàng)
Options:
-h, --help output usage information
-V, --version output the version number
-c, --config specify tars config file. NOTE: independent config will be override this
-n, --config specify tars config file. NOTE: independent config will be override this
-l, --log specify log file
-i, --instances launch [number] instances (for networked app)(load balanced)
--env <environment_name> specify environment to get specific env variables (for JSON declaration)
--http-address <http_address> specify http ip:port address to pass to script - e.g. 127.0.0.1:80
--script-args <script_args> space delimited arguments to pass to script - e.g. --use="https"
--node-args <node_args> space delimited arguments to pass to node - e.g. --node-args="--debug=7001 --trace-deprecation"
--run-as-user <run_as_user> The user or uid to run a managed process as
--run-as-group <run_as_group> The group or gid to run a managed process as
--max-memory-restart specify max memory amount used to autorestart (in megaoctets)
--graceful-shutdown specify graceful shutdown timeout (in millisecond), default is 8000ms
--exception-max <exp_max> The program will be terminated if an exceeding max exception count, default is 5
--exception-time <exp_time> The program will be terminated if an exception occurs within a particular period of time, default is 5000ms
--keepalive-time <detect_time> specify the interval for detecting the worker which could be set to [off] if you want to debug and the default value is 60s
--applog-max-files <applog_max_files> specify max number of rolling log, default is 10
--applog-max-size <applog_max_size> specify max file size for each rolling log, use human readable unit in [K|G|M], default is 10M
--applog-level <applog_level> define log level, default is DEBUG
--tars-node <tars_node> set tars node conncetion string, agent would send notifications to tars node - e.g. tars.tarsnode.ServerObj@tcp -h 127.0.0.1 -p 10000 -t 60000
--tars-local <tars_local> set local interface setup string, agent would receive the notifications from tars node - e.g. tcp -h 127.0.0.1 -p 10000 -t 3000
--tars-monitor <tars_monitor> enable or disable service monitor running in tars platform, and the default value is on
--tars-monitor-http-threshold <http_threshold> if the http(s) status code is large than the preseted threshold then this request will be considered error. default threshold is 400, set it "off" to disabled
--tars-monitor-http-seppath <http_seppath> separate url pathname as interface name, default is on

-c, --config
如果此服務(wù)為TARS服務(wù),可以在此制定服務(wù)的配置文件。
配置文件將自動(dòng)讀入作為基礎(chǔ)配置,通過設(shè)置其他的配置參數(shù)可以覆蓋讀入的基礎(chǔ)配置。

-n, --name
可以在此制定服務(wù)名。

  • 如未配置,則使用腳本的文件名
  • 如魏TARS服務(wù),則服務(wù)名必須為app.serverName格式

-l, -log
指定輸出的日志文件根目錄
如未配置,則所有日志輸出采用stdout/stderr輸出

-i, --instance
node-agent 采用Node.js原生的Cluster模塊來實(shí)現(xiàn)負(fù)載均衡。
在此配置node-agent啟動(dòng)的子進(jìn)程(業(yè)務(wù)進(jìn)程)數(shù)量:

  • 未配置(或配置為 auto、0),啟動(dòng)的子進(jìn)程的數(shù)量等于CPU物理核心的個(gè)數(shù)。
  • 配置為max,啟動(dòng)子進(jìn)程數(shù)量等于CPU個(gè)數(shù)(所有核心數(shù))。
    如果node-agent是由tarsnode啟動(dòng)的,會(huì)自動(dòng)讀取TARS配置文件中的tars.application.client.asyncthread配置節(jié)。也可以通過 TARS平臺(tái) ->遍及服務(wù) -> 異步線程數(shù) 進(jìn)行調(diào)整。

--env
設(shè)置服務(wù)啟動(dòng)時(shí)的環(huán)節(jié)變量,這里需要使用JSON格式進(jìn)行描述
例如:可以通過這個(gè)配置來傳入當(dāng)前的運(yùn)行環(huán)節(jié)(開發(fā)、生產(chǎn))

{\"NODE_ENV"\:\"production"\}

請(qǐng)注意:當(dāng)作為命令參數(shù)傳遞時(shí),這里的雙引號(hào)('')需要進(jìn)行轉(zhuǎn)移('')
如果此服務(wù)為TARS服務(wù),則此參數(shù)以tarsnode可識(shí)別的方式讀取并設(shè)置。

--http-address

設(shè)定服務(wù)腳本執(zhí)行所需的ip:port
在腳本中可以使用環(huán)境變量HTTP_IP(IP)、HTTP_PORT(PORT)進(jìn)行獲?。?/p>

process.env.HTTP_IP
process.env.HTTP_PORT

如果此服務(wù)為TARS服務(wù),則這里的值為配置文件中,第一個(gè)非TARS協(xié)議的Servant指明的ip:port

--script-args

設(shè)置服務(wù)腳本執(zhí)行所需傳入的參數(shù)
例如:

$ node-agent app.js --script-args=''--use="https"

等同于

$ node app.js --use="https"

--node-args

設(shè)置node cluster子進(jìn)程所需的啟動(dòng)參數(shù)
例如:

$ node-agent app.js --node-args="--debug=7001 --trace-deprecation"

等同于:

$ node --debug=7001 --trace-deprecation app.js

--run-as-user, --run-as-group

制定node cluster子進(jìn)程運(yùn)行的用戶(組)
可通過此服務(wù)腳本進(jìn)行降權(quán)執(zhí)行,如未配置權(quán)限等同于node-agent啟動(dòng)用戶(組)

--max-memory-restart

制定服務(wù)所能使用到的最大內(nèi)存。
如果子進(jìn)程達(dá)到最大內(nèi)存限制,將會(huì)拋出異常并退出。
此(資源形)也會(huì)納入整體的異常進(jìn)行處理。

--graceful-shutdown

正常情況下,node-agent在停止服務(wù)(進(jìn)程)時(shí)會(huì)通過worker.disconnect()通知服務(wù),讓服務(wù)釋放資源并退出。在這里可以設(shè)置超時(shí)時(shí)間,如果服務(wù)(進(jìn)程)在給定的時(shí)間后仍然沒有退出,node-agent則會(huì)強(qiáng)制kill掉進(jìn)程,超時(shí)時(shí)間默認(rèn)為8秒。
如果node-agent是由transnode啟動(dòng)的,會(huì)自動(dòng)讀取TARS配置文件中的tars.application.server.deactivating-timeout配置節(jié)。

--exception-max, --exception-time

如果(服務(wù))子進(jìn)程出現(xiàn)異常退出,并在一段時(shí)間內(nèi)(--exception-time)異常退出的次數(shù)沒有超過最大值(--exception-max)。node-agent將會(huì)自動(dòng)拉起新的(服務(wù))子進(jìn)程,否則node-agent與服務(wù)也將異常退出、
以方便第三方管理工具對(duì)服務(wù)狀態(tài)進(jìn)行監(jiān)控:
--exception-time默認(rèn)為10s
--exception-max默認(rèn)為2次

--keepalive-time

如果node-agent在一段時(shí)間(--keepalive-time)內(nèi)未收到(服務(wù))子進(jìn)程發(fā)送的心跳,則判定(服務(wù))子進(jìn)程為僵尸進(jìn)程(zombie process),將會(huì)直接殺死kill,并作為異常進(jìn)行處理。
當(dāng)服務(wù)器可用內(nèi)存過小時(shí)不觸發(fā)此邏輯。
如果您想對(duì)服務(wù)腳本進(jìn)行(端點(diǎn))調(diào)試,這需將此設(shè)置為 --keepalive-time=off
其默認(rèn)值為5m

--applog-max-flies, --applog-max-size, --applog-level

制定服務(wù)默認(rèn)的滾動(dòng)日志大?。?-applog-max-size)、總數(shù)(--applog-max-files)與日志級(jí)別(--applog-level)。
服務(wù)啟動(dòng)時(shí)會(huì)創(chuàng)建兩份(滾動(dòng))日志:

  • app.serverName.log:所啟動(dòng)服務(wù)的 stdout/stderr/console
  • app.serverName_agent.log: node-agent的狀態(tài)信息
    這個(gè)配置主要是影響上面兩份(滾動(dòng))日志的輸出參數(shù)

--tars-node, --tars-local

如果node-agent是由tarsnode啟動(dòng)的,則需要指定tarsnode的RPC連接參數(shù)(--tars-node)與本地被調(diào)的啟動(dòng)參數(shù)(--tars-local)。
此設(shè)置也可以通過TARS配置文件(--tars-config)進(jìn)行指定。
node-agent會(huì)在服務(wù)啟動(dòng)時(shí)間向tarsnode上報(bào)服務(wù)的版本,并在服務(wù)運(yùn)行過程中發(fā)送心跳包。
與此同時(shí),node-agent本地啟動(dòng)的(被調(diào))服務(wù)也將從tarsnode中接收下發(fā)的消息(shutdown/message),并進(jìn)行響應(yīng)。

--tars-monitor

如果您的服務(wù)在TARS平臺(tái)上運(yùn)行的,node-agent會(huì)自動(dòng)向tarsstat上報(bào)服務(wù)的監(jiān)控(用量)信息。
默認(rèn)值為on,設(shè)置為off可關(guān)閉自動(dòng)上報(bào)功能。
具體詳情可查看,監(jiān)控與用量上報(bào)節(jié)。

--tars-monitor-http-threshold

如果您服務(wù)的HTTP(s)返回碼大于此閾值則此次請(qǐng)求將作為異常訪問進(jìn)行上報(bào)。
默認(rèn)response.statusCode >= 400則為異常訪問。
設(shè)置為off可以關(guān)閉此特性。
具體詳情可查見,監(jiān)控與用量上報(bào)節(jié)。

--tars-monitor-http-seppath

HTTP(s)服務(wù)在上報(bào)時(shí)是否需要區(qū)分不同路徑。
默認(rèn)為區(qū)分路徑,其中url.pathname的部分會(huì)作為服務(wù)的接口名進(jìn)行上報(bào)。
如果您的服務(wù)擁有非常多(大基數(shù))的pathname(如RESTful),可設(shè)置為off。具體詳情可查看監(jiān)控與用量上報(bào)節(jié)。

配置

node-agent支持以多種匹配方式進(jìn)行啟動(dòng):

  • 命令行參數(shù)進(jìn)行指定
  • 在服務(wù)腳本的package.son中指定
  • 在TARS服務(wù)的配置文件中指定
    其中:
  • 在package.json或TARS配置文件中指定的值,會(huì)覆蓋掉命令行參數(shù)中指定的配置項(xiàng)。
  • 可以通過駝峰式寫法將配置參數(shù)聲明在package.json中nodeAgent的配置項(xiàng)。
  • TARS服務(wù)的配置文件中以配置參數(shù)原型直接進(jìn)行聲明。
    例如(以nobody用戶啟動(dòng)子進(jìn)程):
    命令行參數(shù):
node-agent app.js -run-as-user=nobody

package.json:

{
  "nodeAgent" : {
    "runAsUser" : "nobody"
  }
}

TARS配置文件:

<tars>  
 <application>  
   <server>  
     run-as-user=nobody  
   </server>  
 </application>  
</tars>  
消息與事件

一般情況下,用戶代碼無需處理(關(guān)注)進(jìn)程消息與事件,單如果您想處理(響應(yīng)):進(jìn)程退出、TARS管理命令,則需要進(jìn)行處理、

process.on('disconnect', function)
關(guān)于此事件具體說明請(qǐng)參考 Cluster Event:‘disconnect’
默認(rèn)情況下 node-agent 會(huì)對(duì)該事件進(jìn)行處理,但如果用戶代碼監(jiān)聽(處理)了該事件則 node-agent 將不再進(jìn)行處理。
請(qǐng)注意:您在處理完該事件后,請(qǐng)一定要顯示調(diào)用process.exit()以確保進(jìn)程可以正常退出。

process.on('message', object)
一旦node-agent收到了tarsnode的管理命令,將會(huì)通過進(jìn)程消息發(fā)送給業(yè)務(wù)腳本。傳遞的消息object的格式為:

{
  cmd: String,
  data: String
}

支持的消息cmd有:

  • tars.viewstatus : 查看服務(wù)狀態(tài)
  • tars.setloglevel:設(shè)置日志等級(jí)
  • tars.loadconfig:PUSH配置文件
  • tars.connection:查看當(dāng)前連接情況
  • 自定義命令
    *node-agent會(huì)對(duì)自定義命令進(jìn)行切分,命令中第一個(gè)空格前的字符作為cmd,后續(xù)的部分作為data。
日志

node-agent會(huì)將服務(wù)的輸出(stdout|stderr管道以及console模塊的輸出)重定向到指定的文件(當(dāng)使用 -l --log參數(shù) 啟動(dòng)時(shí))或者管道。
日志的輸出由winston-tars模塊實(shí)現(xiàn),其輸出的日志格式為:日期 時(shí)間|PID|日志級(jí)別|文件名:行號(hào)|內(nèi)容
服務(wù)腳本可以通過node自帶的console模塊輸出不同級(jí)別的日志。

console.info = INFO
console.log = DEBUG
console.warn = WARN
console.error = ERROR

也可以通過服務(wù)stdout|stderr管道輸出。

process.stdout = INFO
process.stderr = ERROR

日志級(jí)別的優(yōu)先級(jí)為:INFO < DEBUG < WARN < ERROR < NONE
其中,默認(rèn)的日志級(jí)別為:DEBUG

環(huán)境變量

node-agent通過環(huán)境變量向服務(wù)腳本提供所需的變量:

  • process.env.IP:HTTP(s)可監(jiān)聽的IP。
  • process.env.PORT:HTTP(s)可監(jiān)聽的端口。
  • process.env,WORKER_ID 進(jìn)程順序ID(例如啟動(dòng)8個(gè)進(jìn)程,第一個(gè)為0,第二個(gè)為1,以此類推),重新啟動(dòng)的進(jìn)程仍然使用之前的ID。
    如服務(wù)是由tarsnode啟動(dòng)的,還支持如下變量:
  • process.env.TARS_CONFIG:啟動(dòng)服務(wù)所使用的TARS配置文件的絕對(duì)路徑。
  • process.env,TARS_MONITOR:是否開啟監(jiān)控(特性)上報(bào)(統(tǒng)計(jì))。
    請(qǐng)注意:環(huán)境變量全為String類型

監(jiān)控與用量上報(bào)

如果您的服務(wù)是在TARS平臺(tái)上運(yùn)行的,node-agent會(huì)自動(dòng)向tarsstat上報(bào)服務(wù)的監(jiān)控(用量)信息。

監(jiān)控信息

監(jiān)控信息的上報(bào)與您啟動(dòng)的服務(wù)及其調(diào)用者有關(guān)(可通過TARS平臺(tái)->服務(wù)監(jiān)控查看)

  • HTTP(s)
    • 服務(wù)端:response.statusCode >= 400為失敗,所有請(qǐng)求超時(shí)為0
      • 可通過 --tars-monitor-http-threshold與--tars-monitor-http-seppath進(jìn)行配置
        更多詳情您可訪問@tars/monitor.stat獲取。
用量信息

無論您啟用的服務(wù)是什么類型,用量信息總是上報(bào)(可通過 TARS平臺(tái)->特性監(jiān)控 查看):

  • memoryUsage: 內(nèi)存用量,將會(huì)上報(bào)rss、heapUsed、heapTotal這三個(gè)用量(單位為字節(jié))
  • cpuUsage: CPU用量,將會(huì)上報(bào)CPU使用率,數(shù)據(jù)匯總為邏輯單核(單位為百分比)
  • eventloopLag: 事件循環(huán)滯后(V8消息隊(duì)列延遲),每隔2秒采樣(單位為毫秒)
  • libuv: I/O用量,將會(huì)上報(bào) activeHandles、activeRequests 這兩個(gè)用量。
    所有的用量信息的統(tǒng)計(jì)策略均為:Avg、Max、Min
無損操作

如果您的服務(wù)是在TARS平臺(tái)上運(yùn)行的,每次無損重啟或發(fā)布時(shí):

  1. 設(shè)置流量狀態(tài)為無流量(包括路由和第三方流量)
  2. 等待調(diào)用方獲取配置(默認(rèn)為2分13秒)
  3. 執(zhí)行對(duì)應(yīng)操作(重啟或發(fā)布)
  4. 恢復(fù)流量狀態(tài)
    請(qǐng)注意:如果大量節(jié)點(diǎn)同時(shí)進(jìn)行無損操作,會(huì)同時(shí)屏蔽這些節(jié)點(diǎn)的流量,可能會(huì)造成服務(wù)不穩(wěn)定,建議采用無損分批重啟
預(yù)熱

在無損操作的服務(wù)啟動(dòng)過程中,可以選擇是否需要進(jìn)行預(yù)熱:

  1. 服務(wù)啟動(dòng)后美妙檢查是否所有子進(jìn)程都監(jiān)聽了端口(所有子進(jìn)程狀態(tài)均為ONLINE)
  2. 如果超高跟預(yù)熱超時(shí)時(shí)間,且并非所有子進(jìn)程都監(jiān)聽了端口,則無損操作流程失敗并通知用戶(郵件通知)
    我們強(qiáng)烈建議您:在任何情況下,請(qǐng)完成所有初始化操作后再監(jiān)聽(listen)端口
架構(gòu)
architecture.png

node-agent在啟動(dòng)(也就是執(zhí)行cluster.fork)服務(wù)腳本時(shí),并不會(huì)直接載入對(duì)應(yīng)腳本,而是載入node-agent/ProcessContainer.js來對(duì)服務(wù)腳本進(jìn)行包裝,之后再調(diào)用系統(tǒng)的require載入執(zhí)行腳本

deploy

@tars/deploy
TARS框架服務(wù)打包工具,用于打包服務(wù)生成適合TARS框架的發(fā)布包。

安裝
npm install -g @tars/deploy

由于tars-deploy是一個(gè)CLI程序,所以需要使用-g參數(shù)來安裝

用法
tars-deploy name [options]
  • name為服務(wù)的“服務(wù)名”,如您的服務(wù)名為Server,那么填寫“Server”
  • [options]可選配置,詳見選項(xiàng)節(jié)。
    打包時(shí):請(qǐng)切換當(dāng)前目錄到服務(wù)的根目錄()
選項(xiàng)
Options:
  -h, --help output usage information
  -V, --version output the version number
  -f, --force Force to Build Package

-f, --force
由于工具會(huì)打包當(dāng)前的運(yùn)行環(huán)節(jié)(如node可執(zhí)行的二進(jìn)制文件,在當(dāng)前架構(gòu)上重新編譯C/C++ addon等)所以請(qǐng)?jiān)谂c目標(biāo)運(yùn)營架構(gòu)相同的環(huán)境(linux)上執(zhí)行打包工具。
打開此開關(guān),可以跳過此限制。但同時(shí)我們強(qiáng)烈您,不要這么做!

rpc

TARS框架中的RPC通信模塊
00-安裝

$ npm install @tars/tars

01-tars簡介
tars是Tars4NodeJS項(xiàng)目底層的RPC調(diào)用框架,提供了一個(gè)多服務(wù)器進(jìn)程間進(jìn)行RPC調(diào)用的基礎(chǔ)設(shè)置。簡單來說我們可以用這個(gè)模塊做這些事情:

  • 使用tars2node將Tars文件翻譯成客戶端代理類代碼后,供客戶端調(diào)用任意的Tars服務(wù)。
  • 使用tars2node將Tars文件翻譯成服務(wù)端代碼后,可以實(shí)現(xiàn)標(biāo)準(zhǔn)的Tars服務(wù),該服務(wù)可被任意使用TARS/TUP協(xié)議的客戶端直接調(diào)用。
  • 遠(yuǎn)程日志、染色日志、屬性上報(bào)、告警上報(bào)、tarsnode與服務(wù)通信等框架內(nèi)服務(wù)。
  • 創(chuàng)建自定義通信協(xié)議的客戶端代理類(比如使用JSON格式的協(xié)議)。
  • 創(chuàng)建自定義通信協(xié)議的服務(wù)端(比如使用JSON格式的協(xié)議)。
  • 模塊:@tars/registry,功能:根據(jù)服務(wù)Obj名字到主控查詢該服務(wù)可用的IP列表。
    tars分為客戶端和服務(wù)器端兩個(gè)部分??蛻舳瞬糠痔峁┝藃pc代理生成,消息路由和網(wǎng)絡(luò)通訊等功能。服務(wù)器端提供了遠(yuǎn)程服務(wù)暴露,請(qǐng)求派發(fā),網(wǎng)絡(luò)通訊等功能。
    02-關(guān)于協(xié)議、Tars文件以及翻譯工具tars2node的說明
    在深入學(xué)習(xí)了tars的相關(guān)知識(shí)之前,我們先理清:TARS編碼協(xié)議、TUP組包協(xié)議、TARS組包協(xié)議三者之間的關(guān)系:
  • TARS編碼協(xié)議是一種數(shù)據(jù)解碼規(guī)則,它將整形、枚舉值、字符串、序列、字典、自定義結(jié)構(gòu)體等數(shù)據(jù)類型按照一定的規(guī)則編碼到二進(jìn)制數(shù)據(jù)流中。對(duì)端接收到二進(jìn)制數(shù)據(jù)流之后,按照相應(yīng)的規(guī)則反序列化可得到原始數(shù)值。
  • TARS編碼協(xié)議是一種數(shù)據(jù)編解碼規(guī)則,它將整形、枚舉值、字符串、序列、字典、自定義結(jié)構(gòu)體等數(shù)據(jù)結(jié)構(gòu)按照一定的規(guī)則編碼到二進(jìn)制數(shù)據(jù)流中,對(duì)端接收到二進(jìn)制數(shù)據(jù)流之后,按照相應(yīng)的規(guī)則反序列化可得到原始數(shù)據(jù)。
  • TARS編碼協(xié)議使用一種叫做TAG的整型值(unsigned char)來標(biāo)識(shí)變量,比如某個(gè)變量A的TAG值為100(該值由開發(fā)者自定義),我們將變量值編碼的同時(shí),也將該TAG值編碼進(jìn)去。對(duì)端需要讀取變量A的數(shù)值時(shí),就到數(shù)據(jù)流中尋找TAG值為100的數(shù)據(jù)段,找到后按規(guī)則獨(dú)處數(shù)據(jù)部分即是變量A的數(shù)值。
  • TARS編碼協(xié)議的定位是一套編碼規(guī)則。tars協(xié)議序列化之后的數(shù)據(jù)不僅可以進(jìn)行網(wǎng)絡(luò)傳輸,同時(shí)還可以存儲(chǔ)到數(shù)據(jù)庫或者DCache中。
  • TUP組包協(xié)議是TARS編碼協(xié)議的上層封裝,定義為通信協(xié)議。它使用變量名為變量的關(guān)鍵字,編碼時(shí),客戶端將變量名打包到數(shù)據(jù)流中;解碼時(shí),對(duì)端根據(jù)變量名尋找對(duì)應(yīng)的數(shù)據(jù)區(qū),然后根據(jù)數(shù)據(jù)類型對(duì)該數(shù)據(jù)區(qū)進(jìn)行反序列化得到原始數(shù)值。
  • TUP組包協(xié)議內(nèi)置一個(gè)TARS編碼協(xié)議的Map類型,該Map的關(guān)鍵字就是變量名,Map的值是將變量的數(shù)據(jù)值經(jīng)過TARS編碼序列化的二進(jìn)制數(shù)據(jù)。
  • TUP組包協(xié)議封裝的數(shù)據(jù)包可以直接發(fā)給Tars服務(wù)端,而服務(wù)端可以直接反序列化得到原始值。
  • TARS組包協(xié)議是對(duì)RequestPacket(請(qǐng)求結(jié)構(gòu)體)和ResponsePacket(結(jié)果結(jié)構(gòu)體)使用TARS編碼協(xié)議封裝的通信協(xié)議。結(jié)構(gòu)體包含比如請(qǐng)求序列號(hào)、協(xié)議類型、RPC參數(shù)序列化之后二進(jìn)制數(shù)據(jù)等重要信息。
    TARS編碼協(xié)議的編碼規(guī)則以及Tars文件的編寫方法,請(qǐng)參考@tars/steam文檔
    由Tars文件生成客戶端或服務(wù)端代碼的方法:
    首先安裝tars2node模塊,這個(gè)模塊是一個(gè)命令行應(yīng)用程序,所以需要全局安裝
npm install -g @tars/tars2node

通過tars2node xxxx.tars --client命令得到client端代理類
通過tars2node xxxx.tars --server命令得到server端實(shí)現(xiàn)類
tars2node工具簡介
學(xué)習(xí)Tars文件的編寫方法之后,我們可以自己來定義通信描述文件,然后使用tars2node的不同命令行選項(xiàng)生成不同的代碼文件:

$ tars2node Protocol.tars

上述命令將忽略interface描述段,只轉(zhuǎn)換文件中定義的“常量”、“枚舉值”、“結(jié)構(gòu)體”等數(shù)據(jù)類型,供開發(fā)者當(dāng)不使用Tars框架作為調(diào)用工具時(shí)的編解碼庫文件。生成的文件名稱為“ProtocolTars.js”。

$ tars2node Protocol.tars --client

上述命令不僅轉(zhuǎn)換文件中定義的"常量"、"枚舉值"、"結(jié)構(gòu)體"等數(shù)據(jù)類型,同時(shí)將interface的描述段翻譯成RPC調(diào)用框架。生成的文件名稱為"ProtocolProxy.js",該文件供調(diào)用方使用。開發(fā)者引入該文件之后,可以直接調(diào)用服務(wù)端的服務(wù)。具體的使用方法請(qǐng)參考"npm install tars"模塊的說明文檔。

$ tars2node Protocol.tars --server
選項(xiàng) 作用
--tars-lib-path=<DIRECTORY> 指定@tars/stream模塊的路徑,默認(rèn)使用NodeJS的目錄
--with-tars 是否允許"tars"作為命名空間(因?yàn)閠ars這個(gè)命名空間主要用于框架服務(wù)的tars文件定義)
--dir=<DIRECTORY> 生成文件的輸出目錄。
--relative 限定所有的Tars文件都在當(dāng)前目錄尋找。
--tarBase=<DIRECTORY> 指定Tars文件的搜索目錄
--r 轉(zhuǎn)換嵌套的Tars文件(比如在A.tars中包含了B.tars和C.tars,使用該參數(shù),在翻譯A.tars的同時(shí)也將B.tars和C.tars翻譯成JS代碼。)
--client 生成客戶端的調(diào)用類代碼。
--server 生成服務(wù)端的框架代碼。

上述命令不僅轉(zhuǎn)換文件中定義的"常量"、"枚舉值"、"結(jié)構(gòu)體"等數(shù)據(jù)類型,
同時(shí)將interface的描述段翻譯成服務(wù)端的接口文件。生成的文件名稱為"Protocol,js"以及"Protocolmp.js",開發(fā)者不要改動(dòng)"Protocol,js",只需要繼續(xù)完善"Protocollmp.js",實(shí)現(xiàn)文件中具體的函數(shù),即可作為Tars服務(wù)端提供服務(wù)。具體的使用方法請(qǐng)參考"npm install tars"模塊的說明文檔。
tars2node支持的命令行參數(shù)及其作用:

選項(xiàng) 作用
--tars-lib-path=<DIRECTORY> 指定@tars/stream模塊的路徑,默認(rèn)使用NodeJS的目錄
--with-tars 是否允許"tars"作為命名空間(因?yàn)閠ars這個(gè)命名空間主要用于框架服務(wù)的tars文件定義)
--dir=<DIRECTORY> 生成文件的輸出目錄。
--relative 限定所有的Tars文件都在當(dāng)前目錄尋找。
--tarBase=<DIRECTORY> 指定Tars文件的搜索目錄
--r 轉(zhuǎn)換嵌套的Tars文件(比如在A.tars中包含了B.tars和C.tars,使用該參數(shù),在翻譯A.tars的同時(shí)也將B.tars和C.tars翻譯成JS代碼。)
--client 生成客戶端的調(diào)用類代碼。
--server 生成服務(wù)端的框架代碼。
03-tars示例和開發(fā)步驟

文檔看不下去了,馬上動(dòng)手實(shí)測!
第一步,下載rpc模塊代碼
第二步,在rpc模塊根目錄

$ npm install

第三步,在/rpc/examples/rpc-tars/demo.1/server.node.1目錄下

$ node main.js

啟動(dòng)rpc服務(wù)端程序
第四步,在/rpc/examples/rpc-tars/demo.1/client.node.proxy目錄下

$ node main.js

啟動(dòng)rpc客戶端程序
使用tars模塊的開發(fā)步驟
第一步,編寫tars文件,定義客戶端與服務(wù)端同學(xué)用到的常量、枚舉值、結(jié)構(gòu)體、函數(shù)等通信協(xié)議。我們使用如下tars文件作為示例:
一般而言Tars文件通常由服務(wù)端開發(fā)指定、維護(hù)和提供。

module TRom
{
  struct User_t
  {
    0 optional int id = 0;
    1 optional int score = 0;
    2 optional string name = "";
  };

  struct Result_t
  {
    0 optional int id = 0;
    1 optional int iLevel = 0;
  };

  interface NodeJsComm
  {
    int test();
     
    int getall(User_t stUser, out Result_t stResult);
    
    int getUserName(string sUsrName, out string sValue1, out string sValue2);
    
    int setRequest(vector<byte> binRequest, out vector<byte> binResponse);
  };
};

將上述內(nèi)容保存為:NodeJsComm.tars。
第二步,根據(jù)tars文件生成客戶端的調(diào)用代碼

$ tars2node --client NodeJsComm,tars

第三步,客戶端程序

//  STEP01 引入系統(tǒng)模塊以及工具生成的代碼
var Tars = require("@tars/tars").client;
var TRom = require("./NodeJsCommProxy,js").TRom;

//  STEP02  初始化Tars客戶端
//  該步驟非必選項(xiàng),后續(xù)文檔將介紹[tars].client.initalize函數(shù)在什么情況下需要調(diào)用以及它做了哪些工作
//  initalize函數(shù)只需要調(diào)用一次,初始化之后全局可用
//  在演示程序中,我們不需要使用過多的特性,所以先將其注釋
//  Tars.initalize("./config.conf");

//  STEP03  生成服務(wù)端調(diào)用代理類實(shí)例
var prx = Tars.stringToProxy(TRom.NodeJsCommProxy, "TRom.NodeJsTestServer.NodeJsCommObj@tcp -h 127.0.0.1 -p 14002 -t 60000");

//  STEP04  客戶端調(diào)用采用Promise機(jī)制進(jìn)行回調(diào),這里編寫成功以及失敗的回調(diào)函數(shù)
var success = function (result) {
  console.log("result.response.costtime:", result.response.costtime);
  console.log("result.response.return:", result.response.return);
  console.log("result.response,arguments.stResult", result.response,arguments.stResult);
} 

var error = function () {
  console.log("result.response.costtime:", result.response.costtime);
  console.log("result.response.error.code:", result.response.error.code);
  console.log("result.response,error.message", result.response,error.message);
}

//  STEP05 初始化接口參數(shù),開始調(diào)用RPC接口
var stUser = new TRom.User_t();
stUser.name = "tencent-mig";

prx.getall(stUser).then(success, error).done();

將上述代碼保存為client.js,使用如下命令即可調(diào)用服務(wù)端。

$ node client.js
result.response.costtime: 7
result.response.return: 200
result.response.arguments.stResult: {id: 10000, iLevel: 10001}

如果我們只是調(diào)用方,寫到這里已經(jīng)足矣。按照剛才的示例,拿到相應(yīng)Tars文件我們就可以調(diào)用C++的Tars服務(wù)、Java的Tars服務(wù)或者NodeJS的Tars服務(wù)。
第四步,實(shí)現(xiàn)一個(gè)NodeJS版本的Tars服務(wù)。
首先,完形填空。完成Tars文件中定義的RPC函數(shù),實(shí)現(xiàn)自己的業(yè)務(wù)邏輯。
tars2node的--server選項(xiàng)將Tars文件生成服務(wù)端的代碼。使用該選項(xiàng)翻譯工具不僅轉(zhuǎn)換文件中定義的“常量”、“枚舉值”、“結(jié)構(gòu)體”等數(shù)據(jù)類型,同時(shí)將interface描述段翻譯成服務(wù)端的接口文件。主要生成兩個(gè)文件,比如在當(dāng)前例子中會(huì)生成NodeJsComm.js和NodeJsCommImp.js。開發(fā)者不需要也盡量不要改動(dòng)NodeJsComm.js,該文件主要實(shí)現(xiàn)了:結(jié)構(gòu)體編解碼、函數(shù)參數(shù)編解碼、函數(shù)分發(fā)等功能。NodeJsComImp.js繼承于NodeJsComm.js,該文件主要供開發(fā)者填補(bǔ)定義的RPC函數(shù),實(shí)現(xiàn)業(yè)務(wù)邏輯。

var TRom = require('./NodeJsComm.js').TRom;
module.exports.TRom = TRom;

TRom.NodeJsCommImp.prototype.initialize = function() {
  //TODO::
  
}

TRom.NodeJsCommImp.prototype.test = function (current) {
  //TODO::

}

TRom.NodeJsCommImp.prototype.getall = function (current, stUser, stResult) {
  //TODO::
    //初始時(shí),每個(gè)RPC函數(shù)都為空,需要開發(fā)者自己完形填空,補(bǔ)齊這里缺失的業(yè)務(wù)邏輯。
    //補(bǔ)齊業(yè)務(wù)邏輯之后,開發(fā)者調(diào)用current的sendResponse函數(shù),返回?cái)?shù)據(jù)給調(diào)用方。
    //需要注意:每個(gè)函數(shù)的sendResponse都是不一樣的,它的參數(shù)與當(dāng)前函數(shù)的返回值和出參相對(duì)應(yīng)。
    //  如果當(dāng)前函數(shù)有返回值,那么current.sendResponse的第一個(gè)參數(shù)應(yīng)該是該返回值。
    //  示例中當(dāng)前函數(shù)的返回值為int類型,解決返回值的問題之后,我們按順序?qū)懭氘?dāng)前的出參即可。參數(shù)的編解碼和網(wǎng)絡(luò)傳輸由框架解決。
    stResult.id = 10000;
    stResult.iLevel = 10001;

    current.sendResponse(200, stResult);
}

TRom.NodeJsCommImp.prototype.getUsrName = function(current, sUsrName, sValue1, sValue2) {
  //TODO::

}

TRom.NodeJsCommImp.prototype.setRequest = function(current, binRequest, binResponse) {
  //TODO::

}

接下來,創(chuàng)建一個(gè)服務(wù)入口文件。它主要負(fù)責(zé)讀取配置文件、配置端口、設(shè)置協(xié)議解析器、啟動(dòng)服務(wù)等等工作。

var Tars = require("@tars/tars").server;
var TRom = require("./NodeJsCommImp,js").TRom;

var svr = Tar.createServer(TRom.NodeJsCommImp);
svr.start({
  name : "TRom.NodeJSTestServer.NodeJSCommObjAdapetr",
  servant : "TRom.NodeJSTestServer.NodeJsCommObj",
  endpoint : "tcp -h 127.0.0.1 -p 14002 -t 10000",
  protocol : "tars",
  maxconns : 200000
});

console.log("server started.");

將上述代碼保存為server.js,使用如下命令啟動(dòng)。

$ node server.js
server started

04-客戶端的初始化函數(shù)[tars].client.initialize

在演示代碼中我們提到initialize不一定要顯示調(diào)用,我們用其他方式同樣可以設(shè)置我們需要的參數(shù)。
首先我們看下配置文件的格式和必要參數(shù):

<tars>
  <applictaion>
    <client>
      locator = tars.tarsregistry.Queryobj@tcp -h 172.27.208.171 -p 17890 ##定義主控地址
      async-invoke-timeout=60000 ##異步調(diào)用的超時(shí)時(shí)間(ms)
    </client>
  </applictaion>
</tars>

這個(gè)配置文件正是由tarsnode生成的,我們主要使用“tars.application.client.locator”和“tars.application.client.async-invoke-timeout”這兩個(gè)配置項(xiàng)。
什么情況下可以不用調(diào)用initialize函數(shù)?
如果我們在生成服務(wù)端代理時(shí),每個(gè)服務(wù)端都使用直連的模式,也就是在stringToProxy中指定Ip地址就可以不用初始化了。

var Tars = require("@tars/tars").client;

Tars.set("locator", "tars.tarsregistry.QueryObj@tcp -h 172.27.208.171 -p 17890");
Tars.set("timeout", 60000);

上述的調(diào)用方法,與使用initialize+配置文件的方式等價(jià)。

05-Tars服務(wù)的創(chuàng)建方法

tars有三種方法創(chuàng)建一個(gè)標(biāo)準(zhǔn)的Tars服務(wù):
第一種,使用tarsnode生成的配置文件。
使用這種方法與TARS4C++的使用方式一樣。
首先需要我們在TARS管理平臺(tái)配置服務(wù)的Obj,然后在啟動(dòng)程序時(shí)由tarsnode生成包含監(jiān)聽端口的配置文件,然后服務(wù)框架再依賴該配置綁定端口+啟動(dòng)服務(wù)。

deploy.png

tarsnode生成的配置文件類似于如下:

<tars>
  <applictaion>
    enableset=n
      setdivision=NULL
      <server>
        node=tars.tarsnode.ServerObj@tcp -h 127.0.0.1 -p 19386 -t 60000
        app=TRom
        server=NodeJsTestServer
        localip=127.0.0.1
        netthread=2
        local=tcp -h 127.0.0.1 -p 1002 -t 3000
        basepath = /usr/local/app/tars/tarsnode/data/MTT.NodeJSTest/bin/
        datapath = /usr/local/app/tars/tarsnode/data/MTT.NodeJSTest/data/
        logpath=/usr/local/app/tars/app_log//
        logsize=15M
        config=tars.tarsconfig.ConfigObj
        notify=tars.tarsnotify.NotifyObj
        log=tars.tarslog.LogObj
        deactivating-timeout=3000
        openthreadcontext=0
        threadcontextnum=10000
        threadcontextstack=32768
        closeout=0
        <TRom.NodeJsTestServer.NodeJsCommObjAdapter>
          allow
          endpoint=tcp -h 127.0.0.1 -p 14002 -t 60000
          maxconns=200000
          protocol=tars
          queuecap=10000
          queuetimeout=60000
          servant=TRom.NodeJsTestServer.NodeJsCommObj
          shmcap=0
          shmkey=0
          thread=5
        <TRom.NodeJsTestServer.NodeJsCommObjAdapter/>  
      </server>
      <client>
        locator=tars.tarsregistry.Queryobj@tcp -h 127.0.0.1 -p 17890:tcp -h 127.0.0.1 -p 17890
        refresh=endpoint-interval=60000
        stat=tars.tarsstat.StatObj
        property=tars.tarsproperty.PropertyObj
        report-interval=60000
        sample-rate=1000
        max-sample-count=100
        sendthread=1
        recvthread=1
        asyncthread=3
        modulenam=TRom.NodeJsTestServer
        async-invoke-timeout=60000
        sync-invoke-timeout=3000
      </client>
    </nav>
  </applictaion>
</tars>

我們使用該配置文件創(chuàng)建一個(gè)服務(wù),代碼如下:

//  STEP01 引入關(guān)鍵模塊
var Tars = require("@tars/tars");
var TRom = require("./NodeJsCommImp.js");

//  STEP02 創(chuàng)建一個(gè)服務(wù)的實(shí)例
//  注意這里的配置,在正式環(huán)境時(shí),用process.env.TARS_CONFIG來表示配置文件的路徑
//  也就是:svr.initialize(process.env.TARS_CONFIG, function (server){...});
var svr = new Tars.server();
svr.initialize("./TRom.NodeJsTestServer.config.conf", function (server){
  server.addServant(TRom.NodeJsCommImp, server.Application + "." + server.ServerName + ".NodeJsCommObj");
});

//  STEP03 上步初始化服務(wù)之后,開始啟動(dòng)服務(wù)
svr.start();

第二種,顯示化服務(wù)端信息

//  STEP01 引入關(guān)鍵模塊
var Tars = require("@tars/tars").server;
var TRom = require("./NodeJsCommImp,js").TRom;

//  STEP02 創(chuàng)建一個(gè)服務(wù)的實(shí)例
//  注意這里的“endpoint”和“protocol”為必選項(xiàng),格式必須如下實(shí)例相同
var svr = Tars.createServer(TRom.NodeJsCommImp);
svr.start({
  name : "TRom.NodeJsTestServer.AdminObjAdapter",
  servant : "TRom.NodeJsTestServer.AdminObj",
  endpoint : "tcp -h 127.0.0.1 -p 14002 -t 10000"
  maxconns : 200000
  protocol : "tars"
});

console.log("server started");

第三種,從tarsnode生成的配置文件中,選取部分服務(wù)來啟動(dòng)

//  STEP01 引入關(guān)鍵模塊
var Tars = require("@tars/tars");
var TRom = require("./NodeJsCommImp.js");

Tars.server.getServant('./TRom.NodeJsTestServer.config.conf').forEach(function (config){
  var svr, map;
  map = {
    'TRom.NodeJsTestServer.NodeJsCommObj' : TRom.NodeJsCommImp 
  };

  svr = Tars.server.createServer(map[config.servant]);
  svr.start(config);
});

06-Tars客戶端的實(shí)現(xiàn)原理

client.png

07-Tars服務(wù)端的實(shí)現(xiàn)原理

server.png

08-tars作為客戶端調(diào)用第三方協(xié)議服務(wù)的示例

首先我們先定一個(gè)雙方認(rèn)可的通信協(xié)議,比如我們以Json格式作為通信協(xié)議,格式假定:

//  客戶端 --> 服務(wù)端
{
  p_RequestId : 0,  // 本次調(diào)用的序列號(hào)
  p_FuncName : 'test',  // 本次調(diào)用的函數(shù)名稱
  p_Arguments : ['aa', 'bb'......]  // 本次調(diào)用的函數(shù)參數(shù)
}

//  客戶端 <-- 服務(wù)端  
{
  p_RequestId : 0,  // 本次調(diào)用的序列號(hào)
  p_FuncName : 'test',  // 本次調(diào)用的函數(shù)名稱
  p_Arguments : ['ee', 'ff', ......]  // 本次調(diào)用的返回參數(shù)
}

實(shí)現(xiàn)協(xié)議解析類

//  將文件保存為Protocol.js
var EventEmitter = require("events").EventEmitter;
var util = require("@tars/util");

var stream = function() {
  EventEmitter.call(this);
  this._data = undefined;
  this._name = "json";
}

util.inherits(stream, EventEmitter);

stream.prototype.__defineGetter__("name", function () { return this._name; });

module.exports = stream;

/**
 * 根據(jù)傳入數(shù)據(jù)進(jìn)行打包的方法
 * @param request
 * request.iRequestId : 框架生成的請(qǐng)求序列號(hào)
 * request.sFuncName : 函數(shù)名稱
 * request.arguments : 函數(shù)的參數(shù)列表
 */
stream.prototype.compose = function(data) {
  var str = JSON.stringify({
    p_RequestId : data.iRequstId,
    p_FuncName : data.sFuncName,
    p_Arguments : data.arguments
  });

  var len = 4 + Buffer.byteLength(str);
  var buf = new Buffer(len);
  buf.writeUInt32BE(len, 0);
  buf.write(str, 4);

  return buf;
}

/**
 * 網(wǎng)絡(luò)收取包之后,填入數(shù)據(jù)判斷是否完整包
 * @param data 傳入的data數(shù)據(jù)可能是TCP的各個(gè)分片,不一定是一個(gè)完整的數(shù)據(jù)請(qǐng)求,解析類內(nèi)部做好數(shù)據(jù)緩存工作。
 *
 * 當(dāng)有一個(gè)完整的請(qǐng)求時(shí),解包函數(shù)拋出事件,需按照如下格式補(bǔ)充事件的數(shù)據(jù)成員
 *
 * {
 *  iRequestId : 0, // 本次請(qǐng)求的序列號(hào)
 *  sFuncName : “”, // 本次請(qǐng)求的函數(shù)名稱
 *  Arguments : []  // 本次請(qǐng)求的參數(shù)列表
 * }
 */
stream.prototype.feed = function(data) {
  var BinBuffer = data;
  if (this._data != undefined) {
    var temp = new Buffer(this._data.length + data.length);
    this._data.copy(temp, 0);
    data.copy(temp, this._data.length);
    this._data = undefined;
    BinBuffer = temp;
  }

  for(var pos = 0; pos < BinBuffer.length;) {
    if (BinBuffer.length - pos < 4) {
      break;
    }
    var Length = BinBuffer.readUInt32BE(pos);
    if (pos + Length > BinBuffer.length) {
      break;
    }
    var result = JSON.parse(BinBuffer.slice(pos + 4, pos + Length).toString());
    var request =
    {
      iRequestId : result.P_RequestId,
      sFuncName : result.p_FuncName,
      Arguemnts : result.p_Arguments
    };

    this.emit("message", request);
    pos += Length;
  }

  if (pos != BinBuffer.length) {
    this._data = new Buffer(BinBuffer.length - pos);
    BinBuffer.copy(this._data, 0, pos);
  }
}

/**
 * 重置當(dāng)前協(xié)議解析器
 */
stream.prototype.reset = function () {
  delete this._data;
  this._data = undefined;
}

客戶端使用該協(xié)議解析器,調(diào)用服務(wù)端的代碼:

var Tars = require("@tars/tars").client;
var Protocol = require("./ProtocolClient.js");

var prx = Tars.stringToProxy(Tars.ServantProxy, "test@tcp -h 127.0.0.1 -p 12306 -t 60000");
prx.setProtocol(Protocol)
prx.rpc.createFunc("echo");

var success = function (result) {
  console.log("success");
  console.log("result.response.costtime:", result.response.costtime);
  console.log("result.response.arguments:", result.response.arguments);
}

var error = function () {
  console.log("error");
  console.log("result.response.error.code:", result.response.error.code);
  console.log("result.response.error.message:", result.response.error.message);
}

prx.rpc.echo("tencent", "mig", "abc").then(success, error);

另外,如果想請(qǐng)求某個(gè)特征來發(fā)到某臺(tái)固定的機(jī)器,可以使用如下方法:

prx.getUsrName(param, {
  hashCode.userId
}).then(success, error).done();

獲得客戶端對(duì)象之后,調(diào)用服務(wù)端接口函數(shù),此時(shí)可以傳入hashCode參數(shù),tars會(huì)根據(jù)hashCode來吧請(qǐng)求分配到連接列表中固定的一臺(tái)機(jī)器,需要注意的是:

  • 這里的userId是一個(gè)數(shù)字,二進(jìn)制、八進(jìn)制、十六進(jìn)制都可以,但是轉(zhuǎn)換成10進(jìn)制的數(shù)字最好在16位數(shù)一下。javascript處理高精度數(shù)字會(huì)有精度丟失的問題。
  • 服務(wù)端機(jī)器列表固定時(shí),同一hashCode會(huì)被分配到固定的一臺(tái)機(jī)器,當(dāng)服務(wù)端機(jī)器列表發(fā)生變化時(shí),會(huì)重新分配hashCode對(duì)應(yīng)的機(jī)器。

09-tars作為第三方協(xié)議服務(wù)的示例

首先實(shí)現(xiàn)RPC函數(shù)處理類,注意框架的分發(fā)邏輯:
A. 如果客戶端傳來的函數(shù)名,是處理類的函數(shù),那么框架有限調(diào)用對(duì)應(yīng)函數(shù)。
B. 如果客戶端出來的函數(shù)不是處理的函數(shù),那么調(diào)用該處理類的onDispatch函數(shù),由該函數(shù)負(fù)責(zé)處理該請(qǐng)求。
C. 如果也沒有onDispatch函數(shù),則報(bào)錯(cuò)

//  將該文件保存為:EchoHandle.js
var Handle = function () {
  
}

Handle.prototype.initialize = function () {}
Handle.prototype.echo = function (current, v1, v2, v3) {
  console.log("EchoHandle.echo::", v1, v2, v3);

  current.sendResponse("TX", "TX-MIG");
}

Handle.prototype.onDispatch = function(v1, v2, v3) {
  console.log("EchoHandle.onDispatch::", v1, v2, v3);
}

module.exports = Handle;

服務(wù)端啟動(dòng)函數(shù)的代碼示例:

var Tars = require("@tars/tars").server;
var Protocol = require("./ProtocolClient.js");
var Handle = require("./EchoHandle.js");

var svr = Tars.createServer(Handle);
svr.start({
  endpoint : "tcp -h 127.0.0.1 -p 12306 -t 10000",
  protocol : Protocol
});

09-tars客戶端請(qǐng)求參數(shù)

tars客戶端代理對(duì)象調(diào)用協(xié)議接口函數(shù)時(shí),最后一個(gè)參數(shù)可以傳入一個(gè)對(duì)象,配置一些請(qǐng)求參數(shù),目前支持4種請(qǐng)求參數(shù)。
dyeing
請(qǐng)求染色對(duì)象。染色對(duì)象生成方式詳見@tars/dyeing
context
請(qǐng)求上下文對(duì)象
packetType
請(qǐng)求類型參數(shù),1位單向請(qǐng)求,其他為普通請(qǐng)求
hashCode
請(qǐng)求hash,必須填入js精度安全范圍內(nèi)的數(shù)字(Math.pow(2, 53) - 1)
示例:

prx.getUsrName(param, {
  dyeing: dyeingObj,
  context: {xxx:xxx},
  packetType: 1,
  hashCode: userId
}).then(success, error);

Monitor

TARS框架中用于服務(wù)監(jiān)控、特性監(jiān)控上報(bào)

Monitor是TARS(TUP)服務(wù)與特性監(jiān)控上報(bào)模塊。
它由2個(gè)子模塊構(gòu)成:

  • 服務(wù)監(jiān)控(stat)
  • 特性監(jiān)控(property)
    安裝
npm install @tars/monitor
初始化

如果服務(wù)通過node-agent(或TARS平臺(tái))運(yùn)行,則無需執(zhí)行該方法。
初始化是通過調(diào)用特點(diǎn)模塊的init(data)方法實(shí)現(xiàn)。
data: 可以為tars配置文件路徑或已配置的(@tars/utils).Config實(shí)例。

服務(wù)監(jiān)控(stat)
var stat = require('@tars/monitor').stat;

服務(wù)監(jiān)控主要統(tǒng)計(jì)(上報(bào))每個(gè)請(qǐng)求的成功、失敗、超時(shí)的調(diào)用量,并當(dāng)調(diào)用成功時(shí)額外搜集調(diào)用耗時(shí)。
因?yàn)槠渌K已經(jīng)集成了本模塊,所以一般情況下,服務(wù)腳本無需顯式使用此模塊。
已集成的模塊如下:

  • TUP Client&Server由@tars/rpc進(jìn)行上報(bào)。
  • HTTP(S) Server由node-agent進(jìn)行上報(bào),但由于HTTP(S)協(xié)議的特性所以:
    • 不統(tǒng)計(jì)超時(shí)的調(diào)用量,所有請(qǐng)求的超時(shí)上報(bào)為0。
    • response.statusCode >= 400為失敗調(diào)用,否則為成功調(diào)用。
      如您確定要手動(dòng)進(jìn)行上報(bào),可通過如下代碼進(jìn)行:
stat.report(headers, type[, timeout]);

headers:

  • masterName: 主調(diào)模塊名
  • slaveName: 被調(diào)模塊名
  • interfaceName: 被調(diào)模塊接口名
  • masterIp: 主調(diào)IP
  • slaveIp: 被調(diào)IP
  • slavePort: 被調(diào)端口
  • bFromClient: 客戶端上報(bào)為true,服務(wù)端上報(bào)為false
  • returnValue: 返回值,默認(rèn)為0
    如果被調(diào)為set則headers還需包括如下信息:
  • slaveSetName: 被調(diào)set名
  • slaveSetArea: 被調(diào)set地區(qū)名
  • slaveSetID: 被調(diào)set組名
    如果主調(diào)為set則headers還需包含如下信息:
  • masterSetInfo: 主調(diào)set信息(由setName.setArea.setID構(gòu)成)
    參數(shù)type的取值為stat.Type中的一種,如下所示:
    stat.TYPE:
  • SUCCESS: 成功
  • ERROR: 失敗
  • TIMEOUT: 超時(shí)
    如果type === stat.TYPE.SUCCESS必須上報(bào)響應(yīng)耗時(shí)timeout(整型)
    數(shù)據(jù)上報(bào)后,用戶可在服務(wù)監(jiān)控選項(xiàng)卡中查看上報(bào)的數(shù)據(jù)。

特性監(jiān)控(property)

var property = require('@tars/monitor').property;

特性監(jiān)控上報(bào)的是服務(wù)腳本的自定義特性,它由特性名、特性值、以及統(tǒng)計(jì)方法構(gòu)成(key/value pairs)。

property.create(name, policies)

調(diào)用create方法,會(huì)返回(或創(chuàng)建)一個(gè)上報(bào)對(duì)象,可以通過調(diào)用返回對(duì)象的report(value)方法進(jìn)行上報(bào)。其中name為上報(bào)的特性值名,而policies是統(tǒng)計(jì)方法類的實(shí)例數(shù)組(指定了數(shù)據(jù)的統(tǒng)計(jì)方法)。

property.create('name', [new property.POLICY.Count, new  property.POLICY.Max]);

policies數(shù)組中的實(shí)例對(duì)象不能有重復(fù)的統(tǒng)計(jì)方法。
請(qǐng)注意:不要每次上報(bào)都調(diào)用create獲取上報(bào)對(duì)象,這樣會(huì)造成性能損耗

obj.report(value)

property.create會(huì)返回一個(gè)上報(bào)對(duì)象,可以通過調(diào)用對(duì)象的report方法進(jìn)行上報(bào)。
每次調(diào)用report只能上報(bào)一次數(shù)據(jù),并且value在一般情況下必須為數(shù)值。
數(shù)據(jù)上報(bào)后,用戶可以在特性監(jiān)控中查看上報(bào)的數(shù)據(jù)。

統(tǒng)計(jì)方法

特性監(jiān)控 所上報(bào)的數(shù)據(jù)(也就是在調(diào)用create時(shí))需要制定一種或幾種統(tǒng)計(jì)方法,以便統(tǒng)計(jì)在一段時(shí)間內(nèi)的值,這些方法都在POLICY中定義,它們分別是:

  • POLICY.Max:統(tǒng)計(jì)最大值
  • POLICY.Min:統(tǒng)計(jì)最小值
  • POLICY.Count:統(tǒng)計(jì)一共有多少個(gè)數(shù)據(jù)
  • POLICY.Sum:將所有數(shù)據(jù)進(jìn)行相加
  • POLICY.Avg:計(jì)算數(shù)據(jù)的平均值
  • POLICY.Distr:分區(qū)統(tǒng)計(jì)
    除了property.POLICY.Distr其它均不需要傳遞構(gòu)造參數(shù)

property.POLICY.Distr(ranges)

Distr為分區(qū)間統(tǒng)計(jì),事先劃分區(qū)間,在上報(bào)時(shí)會(huì)自動(dòng)統(tǒng)計(jì)落入這個(gè)區(qū)間的value數(shù)量。
同時(shí),在進(jìn)行數(shù)據(jù)展示時(shí),分區(qū)間統(tǒng)計(jì)展示為餅圖類型。
其中,參數(shù)ranges為數(shù)組,其中的每一項(xiàng)為一個(gè)數(shù)值(Int),并以從小到大的順序排列。
例如:

var monitor = property.create('name', [new property.POLICY.Distr([0, 10, 100, 1000])]);
monitor.report(2);
monitor.report(20);
monitor.report(200);

上面的例子統(tǒng)計(jì)的統(tǒng)計(jì)結(jié)果為:

[0 - 10] = 1
(10 - 100] = 1
(100 - 1000] = 1

每個(gè)區(qū)間都包含右側(cè)不包含左側(cè)(除了第一個(gè)區(qū)間)

上報(bào)間隔

在監(jiān)控上報(bào)中,并非每次調(diào)用report方法均會(huì)上報(bào)數(shù)據(jù),模塊會(huì)搜集一定時(shí)間內(nèi)提交的數(shù)據(jù),并進(jìn)行整合統(tǒng)計(jì)后一次性上報(bào)(單向調(diào)用)。
請(qǐng)注意:配置的上報(bào)間隔不可低于10s,亦不可高于TARS主控的刷新時(shí)間(也就是tars.application.client.refresh-endpoint-interval 配置節(jié))
為了防止循環(huán)調(diào)用,上報(bào)模塊本身的調(diào)用情況由被調(diào)方上報(bào)(也就是單向調(diào)用的上報(bào)邏輯)。

stream

TARS框架的編解碼工具

00-安裝
$ npm install @tars/stream
01-stream模塊基本介紹和使用方法

stream模塊用作Tars(tars/TUP)基礎(chǔ)協(xié)議編解碼庫,使用該模塊可以基于tars協(xié)議描述格式對(duì)數(shù)據(jù)流進(jìn)行編解碼,并能夠與目前使用tars協(xié)議的TARS服務(wù)端以及終端進(jìn)行無障礙通信。
tars編解碼模塊工作流方式一般有如下三種:
第一種,以tars文件作為調(diào)用方和服務(wù)方的通信橋梁(雙方約定最終協(xié)議以tars文件為準(zhǔn))
該tars文件也就是我們常識(shí)說的以".tars"結(jié)尾的協(xié)議描述文件。
該tars文件一般由后臺(tái)開發(fā)制定,前臺(tái)開發(fā)需向后臺(tái)開發(fā)索求經(jīng)評(píng)審確定的tars文件,然后經(jīng)工具轉(zhuǎn)換成適用于NodeJS的編解碼源代碼文件。

module TRom 
{
  struct User_t
  {
    0 optional int id = 0; 
    1 optional float score = 0;
    2 optional string name = "";
  };

  struct Result_t 
  {
    0 optional int id = 0;
  };

  interface NodeJsComm
  {
    int test();

    int getall(User_t stUser, out Result_t stResult);

    int getUsrName(string sUsrName, out string sValue1, out string sValue2);

    int secRequest(vector<byte> binRequest, out vector<byte> binResponse);
  };
};

比如,我們將如上內(nèi)容保存為"Protocol.tars"后,可以使用如下的命令生成不同的文件:

$ tars2node Protocol.tars

上述命令將忽略interface描述段,只轉(zhuǎn)換文件中定義的"常量"、"枚舉值"、"結(jié)構(gòu)體"等數(shù)據(jù)類型,供開發(fā)者當(dāng)不使用Tars框架作為調(diào)用工具時(shí)的編解碼庫文件。生成的文件名稱為"Protocol.js"。

$ tars2node Protocal,tars --client

上述命令不僅轉(zhuǎn)換文件中定義的"常量"、"枚舉值"、"結(jié)構(gòu)體"等數(shù)據(jù)類型,同時(shí)將interface的描述段翻譯成RPC調(diào)用框架。生成的文件名稱為"ProtocolProxy.js",該文件供調(diào)用方使用。開發(fā)者引入該文件之后,可以直接調(diào)用服務(wù)端的服務(wù),具體的使用方法請(qǐng)參考"npm install rpc"模塊的說明文檔。

$ tars2node Protocol.tars --server

上述命令不僅轉(zhuǎn)換文件中定義的"常量"、"枚舉值"、"結(jié)構(gòu)體"等數(shù)據(jù)類型,同時(shí)將interface的描述段翻譯成服務(wù)端的接口文件。生成的文件名稱為"Protocol.js"以及"ProtocolImp.js",開發(fā)者不要改動(dòng)"Protocol.js",只需要繼續(xù)完善"ProtocolImp,js",實(shí)現(xiàn)文件中的具體函數(shù),即可作為Tars服務(wù)端提供服務(wù)、具體的使用方法請(qǐng)參考"npm install rpc"模塊的說明文檔。
第二種,沒有協(xié)議描述文件,需要我們自己手工書寫編解碼代碼時(shí)。
比如服務(wù)后臺(tái)提供購買某件商品的功能,它需要“用戶號(hào)碼”、“用戶昵稱”、“商品編號(hào)”、“商品數(shù)量”等四個(gè)參數(shù)。后臺(tái)對(duì)這四個(gè)參數(shù)的編號(hào)(也就是tars中所指的tag)分別為0、1、2、3。

//  第一步,引入tars/TUP編解碼庫
var Tars = require("@tars/stream");

//  第二步,客戶端按照服務(wù)端要求,對(duì)輸入?yún)?shù)進(jìn)行編碼
var ost = new Tars.OutputStream();
ost.writeUnit32(0, 155069599);  //  寫入"用戶號(hào)碼";在服務(wù)端“0”代表“用戶號(hào)碼”
ost.writeString(1, "KevinTian");  //  寫入“用戶昵稱”;在服務(wù)端“1”代表“用戶昵稱”
ost.writeUnit32(2, 1002121); //  寫入“商品編號(hào)”:在服務(wù)端“2”代表“商品編號(hào)”
ost.writeUnit32(3, 10); //  寫入"商品數(shù)量";在服務(wù)端“3”代表“商品數(shù)量”

//  第三步,客戶端將打包后的二進(jìn)制Buffer發(fā)送給服務(wù)端
send (ost.getBinBuffer().toNodeBuffer()) to server

//  第四步,服務(wù)端從客戶端接收完整的請(qǐng)求二進(jìn)制Buffer
recv (var requestBuffer = new Buffer()) from client

//  第五步,將該請(qǐng)求進(jìn)行解碼反序列化
var ist = new Tars.InputStream(new Tars.BinBuffer(requestBuffer));

var uin = ist.readUInt32(0, true);  //  根據(jù)編號(hào)"0"讀取“用戶號(hào)碼”
var name = ist.readString(1, true); //  根據(jù)編號(hào)“1”讀取“用戶昵稱”
var gid = ist.readInt32(2, true); //  根據(jù)編號(hào)“2”讀取“商品編號(hào)”
var num = ist.readUInt32(3, true);  //  根據(jù)編號(hào)“3”讀取“商品數(shù)量”

//  第六步,根據(jù)相關(guān)傳入?yún)?shù)進(jìn)行相應(yīng)的邏輯操作
console.log("name:", name);
console.log("num:", num);
......
第三種,服務(wù)端接受TUP協(xié)議格式的數(shù)據(jù)
//  第一步,引入tars/TUP編解碼庫
var Tars = require("@tars/stream");

//  第二步,客戶端按照服務(wù)端要求,對(duì)輸入?yún)?shù)進(jìn)行編碼
var tup_encode = new Tars.Tup();
tup_encode.writeInt32("uin", 155069599);  //  服務(wù)端接口函數(shù)”用戶號(hào)碼“的變量名稱為”uin“
tup_encode.writeString("name", "KevinTian");  //  服務(wù)端接口函數(shù)”用戶昵稱“的變量名稱為”name“
tup_encode.writeUInt32("gid", 1002121); //  服務(wù)端接口函數(shù)“商品編號(hào)”的變量名稱為“gid”
tup_encode.writeUInt32("num", 10);  //  服務(wù)端接口函數(shù)“商品數(shù)量”的變量名稱為“uum”

var BinBuffer = tup_encode.encode(true);

//  第三步,客戶端將打包后的二進(jìn)制Buffer發(fā)送給服務(wù)端
send (BinBuffer.toNodeBuffer()) to server

//  第四步,服務(wù)端從客戶端接收完整的請(qǐng)求二進(jìn)制Buffer
recv (var requestBuffer = new Buffer()) from client

//  第五步,將請(qǐng)求進(jìn)行解碼反序列化
var tup_decode = new Tars.Tup();
tup_decode.decode(new Tars.BinBuffer(requestBuffer));

var uin = tup_decode.readInt32("uin");  // 服務(wù)端根據(jù)變量名“uin”讀取“用戶號(hào)碼”
var name = tup_decode.readString("name"); //  服務(wù)端根據(jù)變量名“name”讀取”用戶昵稱“
var num = tup_decode.readInt32("num");  //  服務(wù)端根據(jù)變量名“num”讀取“商品數(shù)量”
var gid = tup_decode.readInt32("gid");  //  服務(wù)端根據(jù)變量名“gid”讀取“商品編號(hào)”

//  第六步,根據(jù)相關(guān)傳入?yún)?shù)進(jìn)行相應(yīng)的邏輯操作
console.log("name:", name);
console.log("num:", num);
......

02-stream支持的數(shù)據(jù)類型以及使用方法

數(shù)據(jù)類型 對(duì)應(yīng)C++語言的數(shù)據(jù)類型
布爾值 bool
整型 char(int8)、short(int16)、int(int32)、long long(int64)
整型 unsigned char(uint8)、unsigned short(uint16)、unsigned int(uint32)
數(shù)值 float(32位)、double(64位)
字符串 std::string
數(shù)據(jù)類型 對(duì)應(yīng)C++語言的數(shù)據(jù)類型
結(jié)構(gòu)體 struct(在Tars框架中需要使用tars2node根據(jù)tars文件來生成JavaScript中的類)
二進(jìn)制Buffer vector<char>(在NodeJs中使用[stream].BinBuffer類型來模擬)
數(shù)組 vector<DataType>(在NodeJs中使用[stream].List(vproto)類型來模擬)
詞典 map<KeyType, DataType> (在NodeJs中使用[stream],Map(kproto, vproto)類型來模擬)

基本數(shù)據(jù)類型(見上表)

數(shù)據(jù)類型 對(duì)應(yīng)C++語言的數(shù)據(jù)類型
結(jié)構(gòu)體 struct(在Tars框架中需要使用tars2node根據(jù)tars文件來生成JavaScript中的類)
二進(jìn)制Buffer vector<char>(在NodeJs中使用[stream].BinBuffer類型來模擬)
數(shù)組 vector<DataType>(在NodeJs中使用[stream].List(vproto)類型來模擬)
詞典 map<KeyType, DataType> (在NodeJs中使用[stream],Map(kproto, vproto)類型來模擬)

復(fù)雜數(shù)據(jù)類型(見上表)
關(guān)于NodeJs中數(shù)據(jù)類型的特別說明
[1] : “復(fù)雜數(shù)據(jù)類型”與“基本數(shù)據(jù)類型”,或者“復(fù)雜數(shù)據(jù)類型”與“復(fù)雜數(shù)據(jù)類型”組合使用可以組成其他高級(jí)數(shù)據(jù)類型。
[2] : 雖然NodeJS中支持Float和Double數(shù)據(jù)類型,但我們不推薦使用,因?yàn)樵谛蛄谢?,?shù)值存在精度缺失,某些情況下會(huì)對(duì)業(yè)務(wù)邏輯造成傷害。
[3] : 我們這里實(shí)現(xiàn)的64位整型實(shí)際上是偽64位,在NodeJs它的原型仍然是Number。
我們都知道Js中的Number類型采用IEEE754雙精度浮點(diǎn)數(shù)標(biāo)準(zhǔn)來表示。IEEE754規(guī)定有效數(shù)字第一位默認(rèn)為1,再加上后面的52位來表示數(shù)值。
也就是說IEEE754提供的有效數(shù)字的精度為53個(gè)二進(jìn)制位,這就意味著NodeJs的Number數(shù)值或者說我們實(shí)現(xiàn)的Int64數(shù)據(jù)類型只能精確表示絕對(duì)值小于2的53次方的整數(shù)。
[4] : 在Javascript中String類型是Unicode編碼,在tars編解碼時(shí)我們將其轉(zhuǎn)換成了UTF8編碼格式;
后臺(tái)服務(wù)程序接受到的字符串是UTF8編碼,如果需要按照GBK編碼的方式處理字符串,需要后臺(tái)程序先做下轉(zhuǎn)碼(UTF8->GBK);
后臺(tái)服務(wù)程序如果使用的是GBK,發(fā)送字符串之前,需要將其轉(zhuǎn)成UTF8編碼。

03-基本類型使用方法

//  必須引入stream模塊
var Tars = require('@tars/stream');

//  使用Tars.OutputStream對(duì)數(shù)據(jù)進(jìn)行序列化
var os = new Tars.OutputStream();

os.writeBoolean(0, false);
os.writeInt8(1, 10);
os.writeInt16(2, 32767);
os.writeInt32(3, 0x7FFFFFFE);
os.writeInt64(4, 8589934591);
os.writeUInt8(5, 200);
os.writeUInt16(6, 65535);
os.writeUInt32(7, 0xFFFFFFEE);
os.writeString(8, "我的測試程序");

//  使用Tars.InputStream對(duì)數(shù)據(jù)進(jìn)行反序列化
var is = new Tars.InputStream(os.getBinBuffer());

var tp0 = is.readBoolean(0, true, false);
console.log("BOOLEAN:", tp0);

var tp1 = is.readInt8(1, true, 0);
console.log("INT8:", tp1);

var tp2 = is.readInt16(2, true, 0);
console.log("INT16:", tp2);

var tp3 = is.readInt32(3, true, 0);
console.log("INT32:", tp3);

var tp4 = is.readInt64(4, true, 0);
console.log("INT64:", tp4);

var tp5 = is.readUInt8(5, true, 0);
console.log("UINT8:", tp5);

var tp6 = is.readUInt16(6, true, 0);
console.log("UINT16:", tp6);

var tp7 = is.readUInt32(7, true, 0);
console.log("UINT32:", tp7);

var tp8 = is.readString(8, true, "");
console.log("STRING:", tp8);

04-復(fù)雜類型前傳-用于表示復(fù)雜類型的類型原型

首先,我們理解一下什么是類型原型!
在C++中,我們可以按如下方法聲明一個(gè)字符串的容器向量:

#include <string>
#include <vector>

std::vector<std::string> vec;
vec.push_back("qzone");
vec.push_back("wechat");

其中std::vector<std::string> vec,std::vector表示容器類型,而std::string則表示該容器所容納的類型原型。
那我們?nèi)绾卧贜odeJs中表示該類型?并能使之于tars編碼庫無縫的融合?
為了解決這個(gè)問題,我們使用如下的方法對(duì)std::vector進(jìn)行模擬,以達(dá)到上述C++代碼能完成的功能:

var Tars = require("@tars/stream");

var abc = new Tars.List(Tars.String);
abc.push("qzone");
abc.push("wechat");

其中:Tars.List(Tars.String),Tars.List表示數(shù)組類型,而Tars.String則用來表示該容器所容納的類型原型。
至此,我們明白類型原型主要是用來與復(fù)雜數(shù)據(jù)類型組合,表示更加復(fù)雜的數(shù)據(jù)類型

數(shù)據(jù)類型 描述
布爾值 [stream].Boolean
整型 [stream].Int8, [stream].Int16, [stream].32, [stream].64, [stream].UInt8, [stream].UInt16, [stream].UInt32
數(shù)值 [stream].Float, [stream].Double
字符串 [stream].String
枚舉值 [stream].Enum
數(shù)組 [stream].List
字典 [stream].Map
二進(jìn)制Buffer [stream].BinBuffer

目前的版本中,我們支持如上的類型原型定義:
為了讓大家更加清晰的理解該概念,我們提前描述一部分復(fù)雜類型在NodeJs中的表示方法。
數(shù)據(jù)類型的詳細(xì)使用方法,請(qǐng)參考后續(xù)的詳細(xì)說明。

var Tars = require("@tars/stream");

// c++語法:std::vector<int>
var abc = new Tars.List(Tars.Int32);
abc.push(10000);
abc.push(10001);

//  c++語法:std::vector<std::vector<std::string>>
var abc = new Tars.List(Tars.List(Tars.String));
var ta = new Tars.List(Tars.String);
    ta.push("ta1");
    ta.push("ta2");
var tb = new Tars.List(Tars.String);
    tb.push("tb1");
    tb.push("tb2");
abc.push(ta);
abc.push(tb);

// c++語法:std::map<std::string, std::string>
var abc = new Tars.Map(Tars.String, Tars.String);
abc.insert("key1", "value1");
abc.insert("key2", "value2");

//  c++語法:std::map<std::string, std::vector<string>>
var abc = new Tars.Map(Tars.String, Tars.List(Tars.String));
var ta = new Tars.List(Tars.String);
    ta.push("ta1");
    ta.push("ta2");
var tb = new Tars.List(Tars.String);
    tb.push("tb1");
    tb.push("tb2");
abc.insert("key_a", ta);
abc.insert("key_b", tb);

//  c++語法:std::vector<char>
var abc = new Tars.BinBuffer();
abc.writeInt32(10000);
abc.writeInt32(10001);

05-復(fù)雜類型-struct(結(jié)構(gòu)體)的使用方法說明

module Ext 
{
  struct ExtInfo {
      0 optional string sUerNamel
      1 optional map<string, vector<byte>> data;
      2 optional map<string, map<string, vector<byte>>> cons;
  };
};

將上述內(nèi)容保存為文件"Demo.tars",然后使用命令"tars2node Demo.tars"生成編碼文件"Demo.js"。
"Demo,js"內(nèi)容如下所示:

var TarsStream = require("@tars/stream");

var Ext = Ext || {};
module.exports.Ext = Ext;

Ext.ExtInfo = function() {
  this.sUserName = "";
  this.data = new TarsStream.Map(TarsStream.String, TarsStream.BinBuffer);
  this.cons = new TarsStream.Map(TarsStream.String, TarsStream.Map(TarsStream.String, TarsStream.BinBuffer));
};

Ext.ExtInfo._write = function (os, tag, value) {os.writeStruct(tag, value);}
Ext.ExtInfo._read = function (is, tag, def) { return is.readStruct(tag, true, def); }
Ext.ExtInfo._readFrom = function(is) {
  var tmp = new Ext.ExtInfo();
  tmp.sUserName = is.readString(0, false, "");
  tmp.data = is.readMap(1, false, TarsStream.Map(TarsStream.String, TarsStream.BinBuffer));
  tmp.cons = is.readMap(2, false, TarsStream.Map(TarsStream.String, TarsStream.Map(TarsStream.String, TarsStream.BinBuffer)));
  return tmp;
};
Ext.ExtInfo.prototype._writeTo = function(os) {
  os.writeString(0, this.sUserName);
  os.writeMap(1, this.data);
  os.writeMap(2, this.cons);
};
Ext.ExtInfo.prototype._equal = function(anItem) {
  return anItem.sUserName === this.sUserName
  && anItem.data === this.data
  && anItem.cons === this.cons;
};
Ext.ExtInfo.prototype._genKey = function() {
  if (!this._proto_struct_name_) {
      this._proto_struct_name_ = 'STRUCT' + Math.random();
  }
  return this._proto_struct_name_;
};
Ext.ExtInfo.prototype.toBinBuffer = function() {
  var os = new TarsStream.OutputStream();
  this._writeTo(os);
  return os.getBinBuffer();
};
Ext.ExtInfo.create = function(is) {
   return Ext.ExtInfo._readFrom(is);
};
方法 限制 描述
_write 開發(fā)者不可用 靜態(tài)函數(shù)。當(dāng)結(jié)構(gòu)體作用類型原型時(shí)使用
_read 開發(fā)者不可用 靜態(tài)函數(shù)。當(dāng)結(jié)構(gòu)體作用類型原型時(shí)使用
_readFrom 開發(fā)者不可用 靜態(tài)函數(shù)。從數(shù)據(jù)流中讀取結(jié)構(gòu)體的數(shù)據(jù)成員值,并生成一個(gè)權(quán)限的結(jié)構(gòu)體示例返回。
_writeTo 開發(fā)者不可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體的數(shù)據(jù)成員寫入指定的數(shù)據(jù)流中。
_equal 開發(fā)者不可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體用作字典類型Key值時(shí)的比較函數(shù)。
_genKey 開發(fā)者不可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體用作字典類型Key值時(shí),內(nèi)部使用該函數(shù)獲得當(dāng)前結(jié)構(gòu)體的別名。
toBinBuffer 開發(fā)者可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體序列化成二進(jìn)制Buffer,返回值類型為require("@tars/stream").BinBuffer。
create 開發(fā)者可用 成員函數(shù)。從數(shù)據(jù)流返回一個(gè)全新的結(jié)構(gòu)體。

對(duì)"module Ext"的說明
Ext在C++中就是命名空間,在JavaScript中我們將它翻譯成一個(gè)Object,該命名空間下所有的常量、枚舉值、結(jié)構(gòu)體、函數(shù)都掛載在該Object之下。
tars文件中描述的結(jié)構(gòu)體的表示方法
首先,結(jié)構(gòu)體翻譯成一個(gè)Object。翻譯程序根據(jù)數(shù)據(jù)類型以及tars文件中定義的默認(rèn)值,生成數(shù)據(jù)成員。除tars中定義的數(shù)據(jù)成員之外,根據(jù)編解碼的需要,翻譯程序?yàn)榻Y(jié)構(gòu)體添加了若干輔助函數(shù)。這些函數(shù)如:_writeTo,在需要結(jié)構(gòu)體序列化為數(shù)據(jù)流的地方,被編解碼庫調(diào)用,該函數(shù)逐個(gè)將數(shù)據(jù)成員寫入數(shù)據(jù)流中。
翻譯程序默認(rèn)添加的輔助函數(shù)

方法 限制 描述
_write 開發(fā)者不可用 靜態(tài)函數(shù)。當(dāng)結(jié)構(gòu)體作用類型原型時(shí)使用
_read 開發(fā)者不可用 靜態(tài)函數(shù)。當(dāng)結(jié)構(gòu)體作用類型原型時(shí)使用
_readFrom 開發(fā)者不可用 靜態(tài)函數(shù)。從數(shù)據(jù)流中讀取結(jié)構(gòu)體的數(shù)據(jù)成員值,并生成一個(gè)權(quán)限的結(jié)構(gòu)體示例返回。
_writeTo 開發(fā)者不可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體的數(shù)據(jù)成員寫入指定的數(shù)據(jù)流中。
_equal 開發(fā)者不可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體用作字典類型Key值時(shí)的比較函數(shù)。
_genKey 開發(fā)者不可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體用作字典類型Key值時(shí),內(nèi)部使用該函數(shù)獲得當(dāng)前結(jié)構(gòu)體的別名。
toBinBuffer 開發(fā)者可用 成員函數(shù)。將當(dāng)前結(jié)構(gòu)體序列化成二進(jìn)制Buffer,返回值類型為require("@tars/stream").BinBuffer。
create 開發(fā)者可用 成員函數(shù)。從數(shù)據(jù)流返回一個(gè)全新的結(jié)構(gòu)體。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容