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 仍然是最好的選擇。

這個網誌中的熱門文章

Web 技術中的 Session 是什麼?

淺談 USB 通訊架構之定義(一)

淺談 USB 通訊架構之定義(二)

NodeJS 與 MongoDB 的邂逅

Koa 2 起手式!