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 是什麼?

NodeJS 與 MongoDB 的邂逅

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

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

JavaScript 好用的 async 異步函數!