iT邦幫忙

2025 iThome 鐵人賽

DAY 3
0
自我挑戰組

Robot Framework 與 Websocket 協議測試系列 第 5

訊息協議介紹: Protobuf

  • 分享至 

  • xImage
  •  

Protobuf 緣起與沿革

Protobuf 是技術魔人公司 Google 內部孵化後開源造福社會的專案,從簡單內部協議緩衝區 (Protocol Buffer) 開始發展,因此得名 Protobuf,後來被魔改加上一堆功能早就不是當年的緩衝資料區,不過基於歷史因素還是保留了緩衝區在名字中。
2001 V1 正式面世,後來經歷兩次大改版,現在流通的以 V3 版本為主,不過大部分框架都在 V1 時期就奠定基礎了。

Protobuf 是什麼?

  • 二進位序列化格式:以緊湊的二進位編碼傳輸,通常可比 JSON 小 10–30 倍、解析更快。
  • 強制結構定義:以 .proto 檔定義訊息結構與型別,並能編譯成多語言 SDK,保證收送兩端訊息一致。
  • 多語言支援:官方與社群提供 C++、Java、Go、Python、C#、Node.js 等多語言實作。
  • 兼顧相容性與持續演進:透過欄位編號與保留策略,支援前後相容與漸進式演進。

為什麼不使用 JSON?

  • 可讀性 vs 效能:JSON 可讀性高、易除錯;但文字格式冗長,解析速度與資料量在高併發或行動網路場景成為瓶頸。
  • 結構與驗證:JSON 缺少內建型別與結構約束,需靠其他文件或額外驗證,容易出現「送出與接收不一致」的錯誤。

與 JSON 對比

  • 體積:Protobuf 二進位傳輸數據量更小,特別適合行動或物聯網等弱網環境。
  • 速度:序列化與反序列化更快,降低 CPU 負載與處理延遲。
  • 可讀性:JSON 可讀性高;Protobuf 不可直讀,需配合 .proto 訊息定義檔與轉檔工具。
  • 契約治理:Protobuf 以 schema 為核心,天然適合大型團隊協作與 API 治理。

與 XML 對比

  • 表現形式:XML 為一般文字且可讀性高,支援豐富標記與屬性;Protobuf 為二進位,需依 .proto schema 解析。
  • 訊息大小:Protobuf 通常明顯小於 XML。XML 的標籤冗長,額外字元多;Protobuf 使用緊湊欄位號與型別編碼。
  • 解析效能:Protobuf 以固定或可變長度編碼,解析速度通常快於以 DOM/SAX 解析的 XML。
  • 型別與契約:XML Schema(XSD)可彈性描述結構但較組成複雜,跨語言開發體驗參差不齊;Protobuf 以簡潔 .proto 檔配合官方產碼器,跨語言一致性高。
  • 擴展與相容性:XML 可透過命名空間與可選元素擴展;Protobuf 以欄位號、reserved 與預設值模式保證前後相容,演進策略更明確。
  • 工具與生態:XML 在配置、文件與舊系統整合成熟(如 SOAP、XSLT);Protobuf 在微服務、gRPC、行動與 IoT 場景更常見。

XML 與 Protobuf 範例對照

  • XML(閱讀性高、但冗長)
<Order>
  <Id>123</Id>
  <UserId>42</UserId>
  <Items>
    <Item>
      <Sku>9001</Sku>
      <Qty>2</Qty>
    </Item>
  </Items>
  <Status>PAID</Status>
  <CreatedAtUnix>1726286400</CreatedAtUnix>
</Order>
  • Protobuf(二進位,不易直讀;下為 .proto 定義,實際傳輸為 bytes)
message Order {
  int64 id = 1;
  int64 user_id = 2;
  repeated Item items = 3;
  Status status = 4;
  int64 created_at_unix = 5;
  message Item { int64 sku = 1; int32 qty = 2; }
  enum Status { STATUS_UNSPECIFIED = 0; PAID = 2; }
}

小結:若需求偏向人類可讀、與現有 XML 工具鏈整合,XML 仍合適;若追求序列化效率、跨語言開發與嚴格契約治理,Protobuf 更具優勢。

JSON、Protobuf、XML 總覽比較表

面向 JSON Protobuf XML
資料格式 文字,可閱讀 二進位,不可直接閱讀 文字,可閱讀,標記語法豐富
體積 小(約較 JSON 減少 10–30 倍) 大(標籤冗長)
序列化與解析效能 低到中(DOM/SAX 成本較高)
結構與型別約束 弱,需額外驗證或 JSON Schema 強,.proto 強制型別與欄位 可用 XSD,但較複雜
跨語言開發 一般靠手寫或工具(OpenAPI 等) 官方/社群產碼齊全,多語言一致 可由 XSD 開發,但體驗不一
前後相容與演進 靠文件與版本策略 欄位號、reserved、預設值策略清晰 命名空間與可選元素,但治理成本高
可讀性與除錯 高,易用 cURL/瀏覽器檢視 低,需工具解析 bytes 高,但冗長
典型場景 公開 API、前端互動、後台工具 微服務內部通訊、gRPC、行動與 IoT 企業整合、舊系統、配置、SOAP
學習與生態 門檻低,生態廣 需理解 .proto 與工具鏈 歷史悠久,工具多但偏重

核心概念

  • message:資料結構的定義單位。
  • field number:欄位的識別碼(1–536870911,建議 1–15 給高頻欄位)。
  • scalar types:int32、int64、bool、string、bytes、float、double 等。
  • repeated:列表欄位。
  • enum:列舉型別。
  • oneof:互斥欄位組,只會存在其中一個。
  • reserved:保留欄位號或名稱,避免未來衝突。

基本使用流程

  1. 撰寫 .proto 檔描述訊息結構與服務介面。
  2. 使用 protoc 編譯,產生目標語言的類別或結構。
  3. 在程式中以產生的類別進行序列化與反序列化。
  4. 以 gRPC 可直接用 .proto 定義 RPC 介面,產生 Client 與 Server 程式碼。

.proto 範例

syntax = "proto3";
package demo.v1;

// 使用者資料
message User {
	int64 id = 1;                 // 高頻欄位優先使用 1–15
	string name = 2;
	string email = 3;
	repeated string roles = 4;     // 多值欄位
}

// 訂單資料
message Order {
	int64 id = 1;
	int64 user_id = 2;
	repeated Item items = 3;
	Status status = 4;
	int64 created_at_unix = 5;    // 以 epoch 傳遞時間,避免時區陷阱

	message Item {
		int64 sku = 1;
		int32 qty = 2;
	}

	enum Status {
		STATUS_UNSPECIFIED = 0;
		PLACED = 1;
		PAID = 2;
		SHIPPED = 3;
		CANCELLED = 4;
	}
}

// 若使用 gRPC,可直接定義服務
service OrderService {
	rpc GetOrder(GetOrderRequest) returns (Order) {}
}

message GetOrderRequest { int64 id = 1; }

程式端序列化範例

  • Python
order = Order(
	id=123,
	user_id=42,
	items=[Order.Item(sku=9001, qty=2)],
	status=Order.Status.PAID,
	created_at_unix=1726286400,
)
# 序列化為 bytes
payload = order.SerializeToString()
# 反序列化
obj = Order()
obj.ParseFromString(payload)
  • Go
o := &demo_v1.Order{
	Id:       123,
	UserId:   42,
	Items:    []*demo_v1.Order_Item{
		{Sku: 9001, Qty: 2},
	},
	Status:        demo_v1.Order_PAID,
	CreatedAtUnix: 1726286400,
}
// 序列化
b, err := proto.Marshal(o)
if err != nil {
	// handle error
}
// 反序列化
var out demo_v1.Order
if err := proto.Unmarshal(b, &out); err != nil {
	// handle error
}

相容性與版本管理

  • 新增欄位:使用新的欄位號,舊客戶端會忽略未知欄位;新客戶端可讀舊訊息。
  • 移除欄位:將欄位號與名稱標記為 reserved,避免未來誤用造成語義衝突。
  • 改型別:避免直接變更既有欄位型別,改以新增欄位並逐步遷移。
  • oneof 遷移:將舊欄位納入 oneof 可能破壞相容性,需審慎評估。

適用情境

  • 高併發、低延遲要求的內部服務通訊與微服務。
  • 行動網路、物聯網等需要節省頻寬的場景。
  • 需要嚴格契約治理與多語言 SDK 的跨團隊協作。

何時仍用 JSON?

  • 公開 API 需容易檢視與測試(例如以 cURL 或瀏覽器快速查看)。
  • 後台工具或小規模、低頻通訊且對延遲不敏感的場景。
  • 可將 Protobuf 作為內部通訊,API 邊界層輸出 JSON。

最佳實務

  • 欄位號規劃:1–15 保留給高頻欄位;之後按模組分段管理。
  • 命名規範:訊息、欄位採一致命名風格,列舉以 PREFIX_UNSPECIFIED = 0 開頭。
  • 時間與數值:跨語言優先用整數表示時間戳與金額(以最小貨幣單位)。
  • 版本治理:移除欄位務必 reserved;重大變更以新訊息或新 package。
  • 相依管理:固定 protoc 與外掛版本,於 CI 中做 schema 檢查與相容性測試。

參考來源與官方 Repo

小結

Protobuf 以二進位、高效能與嚴格的結構定義,補足了 JSON 在效能與契約治理上的不足,如合理規劃欄位號與演進策略,可在維持相容性的前提下持續演進介面。
特別適合高併發與多語言的大型系統之間的高頻訊息收發場景。


上一篇
傳輸協議介紹: WebSocket
下一篇
待測系統構思:一個簡單的搖骰子遊戲
系列文
Robot Framework 與 Websocket 協議測試10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言