概觀

Protocol Buffers 是一種語言中立、平台中立、可擴展的結構化資料序列化機制。

它類似於 JSON,但更小、更快,並會產生原生語言繫結。您只需定義一次資料結構,然後即可使用特殊的產生原始碼,輕鬆地在各種資料流之間讀寫結構化資料,並使用各種語言。

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

Protocol Buffers 解決了哪些問題?

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

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

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional 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 {
  optional string name = 1;
  optional int32 id = 2;
  optional 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 檔案時,您可以指定欄位為 optionalrepeated(proto2 和 proto3),或在 proto3 中將其保留為預設的隱含存在。(在 proto3 中沒有將欄位設定為 required 的選項,並且在 proto2 中強烈不建議使用。如需更多相關資訊,請參閱指定欄位規則中的「Required is Forever」。)

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

欄位也可以是

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

在 proto2 中,訊息可以允許擴充來定義訊息本身之外的欄位。例如,protobuf 程式庫的內部訊息結構描述允許用於自訂、特定於使用案例選項的擴充。

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

在設定選填性 (optionality) 和欄位類型之後,您需要為欄位選擇一個名稱。設定欄位名稱時,有一些事項需要注意。

  • 有時候,在欄位名稱用於生產環境之後,變更它們可能會很困難,甚至是不可能。
  • 欄位名稱不能包含破折號。關於欄位名稱語法的更多資訊,請參閱訊息和欄位名稱
  • 對於重複的欄位,請使用複數形式的名稱。

為欄位指定名稱之後,您需要為其指定一個欄位編號。欄位編號不能被重新分配或重複使用。如果您刪除一個欄位,您應該保留其欄位編號,以防止有人意外地重複使用該編號。

額外資料類型支援

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

歷史

要了解 Protocol Buffers 專案的歷史,請參閱Protocol Buffers 的歷史

Protocol Buffers 開源哲學

Protocol Buffers 在 2008 年開源,旨在讓 Google 以外的開發人員也能享受到我們內部使用的相同優勢。我們透過定期更新語言來支持開源社群,因為我們也會進行這些更改以支援我們的內部需求。雖然我們接受來自外部開發人員的特定 pull request,但我們不能總是優先處理不符合 Google 特定需求的功能請求和錯誤修復。

開發者社群

要接收有關 Protocol Buffers 即將發生的變更的通知,並與 protobuf 開發人員和使用者交流,請加入 Google Group

其他資源