Node.js 也可以使用 Protocol Buffers!
「Protocol Buffers (protobuf)」是一套 Google 所提出的結構化資料的包裝技術,讓資料便於網路傳輸或交換,如同常見的 JSON 和 XML 等技術一般。但相對於其他常見技術,protobuf 設計上更易於用來包裝二進位資料,應用在串流(Streaming)技術上,在資料包裝上也更為節省空間,在包裝或解析上也更有效率。
註一:若採用 JSON,由於原本的設計上並無法處理二進位資料,所以如果要包裝二進位資料,傳統做法會將資料轉換成 base64 的格式,再以字串(String)的格式儲存。因為這等於二次包裝資料,導致處理上非常沒有效率。
註二:與 Google Protocol Buffers 類似的技術還有 MessagePack 及 Facebook 採用的 Apache Thrift,有興趣的人可以自行參考比較。
跨語言的優點
另外,Protocol Buffers 最大的優點,就是擁有跨程式語言的設計,提供了一個標準通用的
.proto
定義方法,讓我們定義資料結構和格式。只需要載入這些我們事先準備好的資料定義,就可以輕易生成給不同語言(如:C++、C#、Go、Java、Objective-C 或 Python)用的資料解析器、包裝方法,讓我們可以在不同的語言之間,解析或包裝相同的結構資料。Protocol Buffers 的使用場景?
若在純粹的 Web 應用下,大多數情況,我們不需要處理二進位資料,或是需要非常精準的資料格式,也不會進行單筆高流量的資料交換,所以使用 JSON 或 XML 已經足以。但若你的應用有串流、二進位資料的需求,Protocol Buffers 就是你可以考慮的選擇。
像是筆者在一些公司專案中,會運用 Message Queuing 進行各種訊息資料傳遞,以達成各種資料處理需求。但由於訊息資料內可能有大大小小等各種資料形式和資料型態需求,導致 JSON 包裝已經完全不敷使用,甚至有效能上的疑慮,這時就會採用 Prorocol Buffers 來打包資料。
安裝 ProtoBuf.js
Google 官方其實並沒有實作 JavaScript 版本的 Protocol Buffers 支援,但還好廣大的社群已經有高手開發出 JavaScript 的模組「ProtoBuf.js」,除了在 Node.js 上可以使用以外,甚至可以在瀏覽器中使用。
所以,如果想在 Node.js 裡使用,可以直接透過 NPM 安裝模組:
npm install protobufjs
補註:Protocol Buffers v3.0.0 beta 2 開始官方支援 JavaScript,未來有機會轉用官方的版本。
使用 .proto 定義自己的資料格式
開始使用 Protocol Buffers 的第一個步驟,就是建立一個
.proto
檔來描述定義一個自己的資料格式相當簡單,一個簡單的定義如下。Product.proto 內容:
package Ecommerce;
message Product {
bool available = 1; // 是否上架(布林值)
string name = 2; // 產品名稱(字串)
string desc = 3; // 產品說明(字串)
float price = 4; // 價格(浮點數)
}
實際上 Protocol Buffers 支援了更多資料格式,有興趣的人可以自行參考官方所整理的表格:「Scalar Value Types」。
使用我們定義的 .proto 來包裝資料
若要包裝資料,要先載入 .proto 檔案裡的資料定義,然後使用此定義去進行接下來的工作,而 ProtoBuf.js 提供了一個 encode 方法來進行資料包裝。
由於經過 Protocol Buffers 包裝後的資料是二進位格式,所以 ProtoBuf.js 提供了 finish 方法輸出成 Node.js 的 Buffer 格式:
var ProtoBuf = require('protobufjs');
// 載入 Product.proto 檔案
ProtoBuf.load('Product.proto', function(err, root) {
if (err)
throw err;
// 並取得 Product 資料定義
var Product = root.lookup('Ecommerce.Product');
// 準備包裝的資料
var data = {
available: true,
name: 'ApplePen',
desc: 'The combination of Apple and Pen',
price: 100.0
};
// 包裝資料後回傳 Buffer 格式(二進位形態)
var msgBuffer = Product.encode(data).finish();
});
解開已包裝的資料
若我們有一個已包裝過的資料(無論是從哪裡收到的資料),可以直接使用 decode 方法去解開它:
var ProtoBuf = require('protobufjs');
// 載入 Product.proto 檔案
ProtoBuf.load('Product.proto', function(err, root) {
if (err)
throw err;
// 並取得 Product 資料定義
var Product = root.lookup('Ecommerce.Product');
// 解開
var data = Product.decode(msgBuffer);
});
二進位資料形態的欄位
前面提到,Protocol Buffers 可以包裝二進位資料,若我們想要設定某個欄位為二進位的資料,可以將其資料型態設為「bytes」:
package MyTest;
message Example {
bytes binData = 1;
}
然後,當我們在包裝資料時,該欄位應該是一個 Buffer 的物件:
var msgBuffer = Example
.encode({
binData: new Buffer('This is binary data')
})
.finish();
解開時,該欄位會是一個 Buffer 物件:
var data = Example.decode(msgBuffer);
// 將 Buffer 內容轉成字串形式輸出
console.log(data.binData.toString());
ProtoBuf.js 的效能表現
Protocol Buffers 這類的技術,不外乎就是把一個執行期的 JavaScript 物件,轉換包裝成二進位、字串等資料格式,使資料訊息便於透過網路和其他媒介傳送。實務上,與 JavaScript 物件轉成 JSON 字串是同樣的意思。
所以若要評估這樣技術的效能,最實際的方式就是測試、比較他們的「轉換」的效率,ProtoBuf.js 官方提供了一些「效能測試」,方便我們在自己機器上進行 Protocol Buffers 與原生 JSON 處理的效能比較。
從官方的測試結果來看,從資料包裝的速度,ProtoBuf.js 的效能快過於「JSON.stringify」將近一倍,如果是轉成二進位形式(to Buffer)更是快三倍左右;從解開包裝的速度來看,ProtoBuf.js 效能則是「JSON.parse」的三至四倍效能以上。
整體比較起來,ProtoBuf.js 則是比純 JSON 的處理快上一倍以上。
節錄官方 Github 上的測試結果(機器:i7-2600K。Node.js 版本:6.9.1):
benchmarking encoding performance ...
Type.encode to buffer x 547,361 ops/sec ±0.27% (94 runs sampled)
JSON.stringify to string x 310,848 ops/sec ±0.73% (92 runs sampled)
JSON.stringify to buffer x 173,608 ops/sec ±1.51% (86 runs sampled)
Type.encode to buffer was fastest
JSON.stringify to string was 43.5% slower
JSON.stringify to buffer was 68.7% slower
benchmarking decoding performance ...
Type.decode from buffer x 1,294,378 ops/sec ±0.86% (90 runs sampled)
JSON.parse from string x 291,944 ops/sec ±0.72% (92 runs sampled)
JSON.parse from buffer x 256,325 ops/sec ±1.50% (90 runs sampled)
Type.decode from buffer was fastest
JSON.parse from string was 77.4% slower
JSON.parse from buffer was 80.3% slower
benchmarking combined performance ...
Type to/from buffer x 254,126 ops/sec ±1.13% (91 runs sampled)
JSON to/from string x 122,896 ops/sec ±1.29% (90 runs sampled)
JSON to/from buffer x 88,005 ops/sec ±0.87% (89 runs sampled)
Type to/from buffer was fastest
JSON to/from string was 51.7% slower
JSON to/from buffer was 65.3% slower
benchmarking verifying performance ...
Type.verify x 6,246,765 ops/sec ±2.00% (87 runs sampled)
benchmarking message from object performance ...
Type.fromObject x 2,892,973 ops/sec ±0.70% (92 runs sampled)
benchmarking message to object performance ...
Type.toObject x 3,601,738 ops/sec ±0.72% (93 runs sampled)
其他使用場景?
只要你有需要跟其他系統、服務、外部程式進行資料交換,Protocol Buffers 就有他適用的地方。
舉例來說,現在很多人開始採用 WebSocket 取代傳統的 Socket,使得 WebSocket 不再只是應用在瀏覽器之中,甚至可能是各種機器與機器之間的溝通。在這種情況下,其中交換、傳遞的資訊可能不是普通純文字這麼簡單,也很有可能是二進位類型、串流形式的資料,導致 JSON 可能因此不適合用於當作其中的資料交換格式。這時,就可以 Protocol Buffers 與 WebSocket 搭配使用。
不只如此,在這 IoT 當道的年代,在這訊息技術滿天飛的年代 AMQP、MQTT 等各種通訊技術下,以及需要許多爆量資料收集分析的場景,Protocol Buffers 也很有發揮的空間。
後記
要注意的是,Protocol Buffers 雖然是個好東西,但並非是個用來完全取代 JSON 的解決方案,JSON 仍有其可讀性高、易操作及通用性高等優點。在多數 API 設計的場景之下,JSON 仍然是最好的選擇。
留言
張貼留言