總覽

Protocol Buffers 是一種與語言和平台無關、可擴充的機制,用於序列化結構化資料。

它類似 JSON,但更小、更快速,而且會產生原生語言繫結。您只需定義一次資料的結構,然後就可以使用特殊產生的原始碼,輕鬆地將結構化資料寫入及讀取到各種資料串流,並使用各種語言。

Protocol buffers 是定義語言 (在 .proto 檔案中建立)、proto 編譯器產生的用於與資料介接的程式碼、語言專屬的執行階段程式庫、寫入檔案 (或透過網路連線傳送) 的資料序列化格式,以及序列化資料的組合。

Protocol Buffers 解決了哪些問題?

Protocol buffers 為類型化結構化資料封包提供序列化格式,這些封包的大小最大可達數 MB。此格式適用於短暫的網路流量和長期資料儲存。Protocol buffers 可以使用新資訊擴充,而不會使現有資料失效或需要更新程式碼。

Protocol buffers 是 Google 最常用的資料格式。它們廣泛用於伺服器間通訊以及磁碟上資料的封存儲存。Protocol buffer 的訊息服務由工程師撰寫的 .proto 檔案描述。以下顯示 message 的範例

edition = "2023";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

proto 編譯器會在建構時在 .proto 檔案上叫用,以產生各種程式設計語言 (在本主題稍後的 跨語言相容性 中涵蓋) 的程式碼,以操作對應的 protocol buffer。每個產生的類別都包含每個欄位的簡單存取器,以及將整個結構序列化和剖析為原始位元組及從原始位元組剖析的方法。以下向您展示一個使用這些產生方法的範例

Person john = Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .build();
output = new FileOutputStream(args[0]);
john.writeTo(output);

由於 protocol buffers 廣泛用於 Google 的各種服務中,而且其中的資料可能會持續一段時間,因此維護回溯相容性至關重要。Protocol buffers 允許無縫支援變更,包括新增欄位和刪除現有欄位,而不會破壞現有服務。如需有關此主題的詳細資訊,請參閱本主題稍後的 更新 Proto 定義而不更新程式碼

使用 Protocol Buffers 有哪些好處?

Protocol buffers 非常適合任何需要以與語言和平台無關、可擴充的方式序列化結構化、記錄式、類型化資料的情況。它們最常用於定義通訊協定 (與 gRPC 結合使用) 和資料儲存。

使用 protocol buffers 的一些優點包括:

  • 精簡的資料儲存
  • 快速剖析
  • 在多種程式設計語言中可用
  • 透過自動產生的類別實現最佳化功能

跨語言相容性

相同的訊息可以由任何受支援程式設計語言編寫的程式碼讀取。您可以讓一個平台上的 Java 程式從一個軟體系統擷取資料,根據 .proto 定義對其進行序列化,然後在另一個平台上執行的個別 Python 應用程式中從該序列化資料中擷取特定值。

protocol buffers 編譯器 protoc 直接支援以下語言:

以下語言由 Google 支援,但專案的原始碼位於 GitHub 儲存庫中。protoc 編譯器針對這些語言使用外掛程式

其他語言並非由 Google 直接支援,而是由其他 GitHub 專案支援。這些語言在 Protocol Buffers 的第三方附加元件 中涵蓋。

跨專案支援

您可以透過在 .proto 檔案中定義 message 類型,在專案之間使用 protocol buffers,這些檔案位於特定專案程式碼庫之外。如果您要定義預期會在您的直接團隊外部廣泛使用的 message 類型或列舉,您可以將它們放在自己的檔案中,而無需任何依賴項。

在 Google 內廣泛使用的 proto 定義範例包括 timestamp.protostatus.proto

更新 Proto 定義而不更新程式碼

軟體產品向後相容是標準做法,但向前相容則較不常見。只要您在更新 .proto 定義時遵循一些 簡單的實務,舊程式碼就能讀取新訊息而不會發生問題,並忽略任何新增加的欄位。對於舊程式碼,已刪除的欄位將具有其預設值,而已刪除的重複欄位將為空。如需有關「重複」欄位為何的資訊,請參閱本主題稍後的 Protocol Buffers 定義語法

新程式碼也將透明地讀取舊訊息。新欄位將不會出現在舊訊息中;在這些情況下,protocol buffers 提供合理的預設值。

Protocol Buffers 在哪些情況下不適用?

Protocol buffers 並非適用於所有資料。尤其是:

  • Protocol buffers 傾向於假設整個訊息可以一次載入到記憶體中,並且不會大於物件圖形。對於超過數 MB 的資料,請考慮不同的解決方案;在處理較大的資料時,您可能會因為序列化副本而有效地得到資料的多個副本,這可能會導致記憶體使用量出現驚人的峰值。
  • 當 protocol buffers 序列化時,相同的資料可以有許多不同的二進位序列化。您無法在未完全剖析兩個訊息的情況下比較它們是否相等。
  • 訊息未壓縮。雖然訊息可以像任何其他檔案一樣進行 zip 或 gzip 壓縮,但專用壓縮演算法 (例如 JPEG 和 PNG 使用的演算法) 將為適當類型的資料產生更小的檔案。
  • 對於許多涉及大型多維浮點數陣列的科學和工程用途,Protocol buffer 訊息在大小和速度方面並非最有效率。對於這些應用程式,FITS 和類似格式的額外負擔較少。
  • Protocol buffers 在科學計算中常用的非物件導向語言 (例如 Fortran 和 IDL) 中不受良好支援。
  • Protocol buffer 訊息本身並未固有地自我描述其資料,但它們具有完全反射的結構描述,您可以使用它來實作自我描述。也就是說,如果您沒有權限存取其對應的 .proto 檔案,就無法完全解譯它。
  • Protocol buffers 並非任何組織的正式標準。這使得它們不適合在具有法律或其他要求以標準為基礎建構的環境中使用。

誰在使用 Protocol Buffers?

許多專案都使用 protocol buffers,包括以下專案:

Protocol Buffers 如何運作?

下圖顯示您如何使用 protocol buffers 來處理資料。

Compilation workflow showing the creation of a proto file, generated code, and compiled classes
圖 1. Protocol buffers 工作流程

protocol buffers 產生的程式碼提供公用程式方法,用於從檔案和串流擷取資料、從資料中擷取個別值、檢查資料是否存在、將資料序列化回檔案或串流,以及其他有用的功能。

以下程式碼範例向您展示 Java 中此流程的範例。如先前所示,這是 .proto 定義

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

編譯此 .proto 檔案會建立一個 Builder 類別,您可以用於建立新執行個體,如下列 Java 程式碼所示

Person john = Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .build();
output = new FileOutputStream(args[0]);
john.writeTo(output);

然後,您可以使用 protocol buffers 在其他語言 (例如 C++) 中建立的方法來還原序列化資料

Person john;
fstream input(argv[1], ios::in | ios::binary);
john.ParseFromIstream(&input);
int id = john.id();
std::string name = john.name();
std::string email = john.email();

Protocol Buffers 定義語法

在定義 .proto 檔案時,您可以指定基數 (單數或重複)。在 proto2 和 proto3 中,您也可以指定欄位是否為選用欄位。在 proto3 中,將欄位設定為選用欄位 會將其從隱含存在性變更為明確存在性

設定欄位的基數後,您需要指定資料類型。Protocol buffers 支援常用的基本資料類型,例如整數、布林值和浮點數。如需完整清單,請參閱純量值類型

欄位也可以是:

  • message 類型,以便您可以巢狀定義部分,例如用於重複資料集。
  • enum 類型,以便您可以指定一組值以供選擇。
  • oneof 類型,當訊息具有許多選用欄位,且在同一時間最多只會設定一個欄位時,您可以使用此類型。
  • map 類型,將鍵值組新增至您的定義。

訊息可以允許擴充功能,以在訊息本身之外定義欄位。例如,protobuf 程式庫的內部訊息結構描述允許針對自訂、特定用途的選項進行擴充。

如需可用選項的詳細資訊,請參閱 proto2proto3edition 2023 的語言指南。

設定基數和資料類型後,您需要為欄位選擇名稱。設定欄位名稱時,需要注意一些事項:

  • 在欄位名稱已在生產環境中使用後,有時可能難以甚至無法變更欄位名稱。
  • 欄位名稱不能包含破折號。如需有關欄位名稱語法的詳細資訊,請參閱訊息和欄位名稱
  • 重複欄位請使用複數名稱。

在為欄位指派名稱後,您需要指派欄位編號。欄位編號不得重新調整用途或重複使用。如果您刪除欄位,您應該保留其欄位編號,以防止有人意外重複使用該編號。

額外資料類型支援

Protocol buffers 支援許多純量值類型,包括同時使用變長編碼和固定大小的整數。您也可以透過定義訊息來建立自己的複合資料類型,這些訊息本身就是您可以指派給欄位的資料類型。除了簡單和複合值類型之外,還發布了幾種通用類型

歷史記錄

若要閱讀 protocol buffers 專案的歷史記錄,請參閱Protocol Buffers 歷史記錄

Protocol Buffers 開放原始碼哲學

Protocol buffers 在 2008 年開放原始碼,目的是為 Google 外部的開發人員提供我們從內部獲得的相同好處。當我們進行這些變更以支援我們的內部需求時,我們會定期更新語言,以支援開放原始碼社群。雖然我們接受外部開發人員的精選提取請求,但我們無法始終優先處理不符合 Google 特定需求的功能請求和錯誤修正。

開發人員社群

若要收到有關 Protocol Buffers 即將發生的變更的通知,並與 protobuf 開發人員和使用者建立聯繫,請加入 Google 網路論壇

其他資源