基於Thrift的Node.js微服物通訊

最近公司開始要把各服務器的通訊方法改為Thrift,開始試著利用Thrift實作Node.js的通訊架構。

Apache Thrift 是 Facebook 實現的一種高效的、支持多種編程語言的遠程服務調用的框架,傳輸數據採用二進制格式,相對 XML 和 JSON 體積更小,對於高並發、大數據量和多語言的環境更有優勢。

講白了就是資料傳輸速度比傳統的Rest架構快非常多。可參考這篇Microservice Showdown – REST vs SOAP vs Apache Thrift (And Why It Matters)

這玩意很單純的利用tcp去傳輸資料,當然使省去很多瑣碎的多餘資料,省資源,效率更高,很適合用在整合散部各處的node.js微服物器。

但是Thrift也不是沒有缺點,省去了很多瑣碎的手續,相對資料的安全性就會降低,首先一大問題就是tcp連線可能隨時會中斷,所以要你要想要作長連線,最好還是乖乖地使用標準的socket協定,基本上還是建議使用在網路比較穩定的區網內服務通訊。

如果你有一個大型服務,需要收集(或請求)分散各處的微型服務,而這些服務都在同一個主機或是穩定的網路區域,就很適合利用Thrift作為通訊架構。

cc.png

以下實作利用Thrift再在Node.js服務器中通訊的方法

  1. 首先建立一個nodejs專案,然後安裝thrift掛件

    1
    npm i thrift
  2. 建立一個介面定義檔

Thrift其實最棒的地方就是支援很多語言,你只要寫一個.thrift定義檔,就可以幫你輸出各種語言的介面函示庫,c、java、php、ruby、python、js等等主流語言。

先做一個基本的定義檔 demoApi.thrift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 介面定義檔
*/
/* 各語言命名空間 這邊不用改*/
namespace cpp demoApi
namespace d demoApi
namespace java demoApi
namespace php demoApi
namespace perl demoApi
namespace haxe demoApi
/* 定義呼叫的方法介面與參數 */
service DemoApi {
void apiCall() /* 定義一個apiCall方法 */
}

這裡定義一個 DemoApi 的服務,這個服務裡面有一個apiCall方法,apiCall沒有接受參數,也沒有回傳值,所以前面加個viod。

  1. 編譯介面檔

demoApi.thrift 只是一個,定義文件,必須透過編譯器再編譯成各種語言的介面函示庫。

首先下載編譯器

下載後編譯你的demoApi.thrift定義檔,語法如下

1
thrift --gen <language> <Thrift filename>

以下針對node.js作編譯

1
hrift -r --gen js:node demoApi.thrift

編譯完成後產生介面函示庫gen-nodejs資料夾,這個就是node.js的介面函示庫。目前裡面應該會有兩隻檔案
DemoApi.js 方法介面的定義
demoApi_types.js 結構的定義(目前我們沒有定義任何結構)

4.開始撰寫nodejs各通訊端點

介面函示庫好了後就可以開始使用了,首先寫Server端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var thrift = require("thrift");
/*
* 匯入 thrift產生的函式庫
*/
var DemoApi = require("./gen-nodejs/DemoApi");
/**
* 定義一個 DemoApi 服務器
*/
var server = thrift.createServer(DemoApi, {
/**
* 最單純的通訊,呼叫定義的apiCall方法,定義的方法請參照demo.thrift
*/
apiCall: function (result) {
console.log("apiCall()");
result(null);
}
});
/**
* 偵聽8080 port
*/
server.listen(8080);

再來是Client端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var thrift = require("thrift");
/*
* 匯入 thrift產生的函式庫
*/
var DemoApi = require("./gen-nodejs/DemoApi");
/**
* 通訊協定定義
*/
var transport = thrift.TBufferedTransport();
var protocol = thrift.TBinaryProtocol();
/**
* 連線
*/
var connection = thrift.createConnection("localhost", 8080, {
transport: transport,
protocol: protocol
});
/**
* 連線錯誤事件
*/
connection.on('error', function (err) {
console.error(err);
});
/**
* 打開一個DemoApi服務通訊流程
*/
var client = thrift.createClient(DemoApi, connection);
/**
* 最單純的通訊,呼叫定義的apiCall方法,定義的方法請參照demo.thrift
*/
client.apiCall(function (err, response) {
console.log('apiCall()');
connection.end(); //事情都作完記得關閉連線,很重要!!!
});

好了之後你可以分別執行server與client,順利的話應該可以看到執行的console.log結果

接下來我們試著傳送一些資料,目前Thrift可以定義的類型如下

  • bool
  • i8 (byte)
  • i16(16位元整數)
  • i32(32位元整數)
  • i64(64位元整數)
  • double(64位元浮點數)
  • string(字串)
  • binary(byte array)

我們在定義檔demoApi.thrift上加上一個新的方法

1
2
3
4
service DemoApi {
void apiCall(), /* 逗號隔開 */
i32 add(1:i32 num1, 2:i32 num2) /* 新增add這個方法 */

add這個方法傳入兩個32位元整數,最前面的i32代表回傳的型態

加上後記得再編譯一次

1
hrift -r --gen js:node demoApi.thrift

Servre端

1
2
3
4
5
6
7
8
apiCall: function (result) {
console.log("apiCall()");
result(null);
},
add: function (n1, n2, result) {
console.log("add(", n1, ",", n2, ")");
result(null, n1 + n2);
}

Client端

1
2
3
client.add(1, 1, function (err, response) {
console.log("1+1=" + response);
});

這個方法是把傳入的兩個數字相加後再return回去。

再次執行後應該可以看到對應的結果!

其他資料型態與完整範例請至我的gitlab

https://gitlab.com/cainplay/Thrift_demo