- Protocol Buffers 教程
- Protocol Buffers - 首頁
- Protocol Buffers - 簡介
- Protocol Buffers - 基本應用
- Protocol Buffers - 結構
- Protocol Buffers - 訊息
- Protocol Buffers - 字串
- Protocol Buffers - 數字
- Protocol Buffers - 布林值
- Protocol Buffers - 列舉
- Protocol Buffers - 重複欄位
- Protocol Buffers - 對映
- Protocol Buffers - 巢狀類
- Protocol Buffers - 可選欄位和預設值
- Protocol Buffers - 語言無關性
- Protocol Buffers - 複合資料型別
- Protocol Buffers - 命令列使用
- Protocol Buffers - 定義更新規則
- Protocol Buffers - 與 Kafka 整合
- Protocol Buffers - 其他語言
- Protocol Buffers 有用資源
- Protocol Buffers - 快速指南
- Protocol Buffers - 有用資源
- Protocol Buffers - 討論
Protocol Buffers - 定義更新規則
概述
假設你已經編寫了將在生產環境中使用的proto檔案的定義。將來顯然會有需要更改此定義的時候。在這種情況下,必須使我們所做的更改遵守某些規則,以便更改具有向後相容性。讓我們透過一些“應該做”和“不應該做”的例子來看一下。
在寫入器中新增一個新欄位,而讀取器保留舊版本的程式碼。
假設你決定新增一個新欄位。理想情況下,要新增新欄位,必須同時更新寫入器和讀取器。但是,在大規模部署中,這是不可能的。在某些情況下,寫入器已更新,但讀取器尚未更新新欄位。這就是上述情況發生的地方。讓我們來看一下實際情況。
繼續我們的劇院示例,假設我們的proto檔案中只有一個'name'標籤。以下是我們需要用來指示 Protobuf 的語法:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
}
要使用 Protobuf,我們現在必須使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:
protoc --java_out=. theater.proto
上述命令應該建立所需的檔案,現在我們可以在 Java 程式碼中使用它。首先,我們將建立一個寫入器來寫入劇院資訊:
TheaterWriter.java
package com.tutorialspoint.theater;
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.build();
String filename = "theater_protobuf_output";
System.out.println("Saving theater information to file: " + filename);
try(FileOutputStream output = new FileOutputStream(filename)){
theater.writeTo(output);
}
System.out.println("Saved theater information with following data to disk: \n" + theater);
}
}
接下來,我們將有一個讀取器來讀取劇院資訊:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
public class TheaterReader{
public static void main(String[] args) throws IOException {
Builder theaterBuilder = Theater.newBuilder();
String filename = "theater_protobuf_output";
System.out.println("Reading from file " + filename);
try(FileInputStream input = new FileInputStream(filename)) {
Theater theater = theaterBuilder.mergeFrom(input).build();
System.out.println(theater);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: name: "Silver Screener"
現在讓我們執行讀取器從同一個檔案讀取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener"
未知欄位
我們根據 Protobuf 定義編寫了一個簡單的字串,並且讀取器能夠讀取該字串。我們還看到讀取器沒有不知道的未知欄位。
但是現在,假設我們要向我們的 Protobuf 定義中新增一個新的字串'address'。現在它看起來像這樣:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
}
我們還將更新我們的寫入器並新增一個address欄位:
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.build();
在編譯之前,將上一次編譯生成的 JAR 檔案重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: name: "Silver Screener" address: "212, Maple Street, LA, California"
現在讓我們執行讀取器從同一個檔案讀取,但使用舊的 JAR 檔案:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output Reading from file theater_protobuf_output name: "Silver Screener" 2: "212, Maple Street, LA, California" Unknown fields: 2: "212, Maple Street, LA, California"
從輸出的最後一行可以看出,舊的讀取器不知道新寫入器新增的address欄位。它只是顯示了“新寫入器 - 舊讀取器”組合是如何工作的。
刪除欄位
假設你決定刪除一個現有欄位。理想情況下,要使刪除的欄位立即生效,必須同時更新寫入器和讀取器。但是,在大規模部署中,這是不可能的。在某些情況下,寫入器已更新,但讀取器尚未更新。在這種情況下,讀取器仍將嘗試讀取已刪除的欄位。讓我們來看一下實際情況。
繼續我們的劇院示例,假設我們的proto檔案中只有兩個標籤。以下是我們需要用來指示 Protobuf 的語法:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
}
要使用 Protobuf,我們現在必須使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:
protoc --java_out=java/src/main/java proto_files\theater.proto
上述命令應該建立所需的檔案,現在我們可以在 Java 程式碼中使用它。首先,我們將建立一個寫入器來寫入劇院資訊:
TheaterWriter
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.build();
String filename = "theater_protobuf_output";
System.out.println("Saving theater information to file: " + filename);
try(FileOutputStream output = new FileOutputStream(filename)){
theater.writeTo(output);
}
System.out.println("Saved theater information with following data to disk: \n" + theater);
}
}
接下來,我們將有一個讀取器來讀取劇院資訊:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
public class TheaterReader{
public static void main(String[] args) throws IOException {
Builder theaterBuilder = Theater.newBuilder();
String filename = "theater_protobuf_output";
System.out.println("Reading from file " + filename);
try(FileInputStream input = new FileInputStream(filename)) {
Theater theater = theaterBuilder.mergeFrom(input).build();
System.out.println(theater);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: name: "Silver Screener" address: "212, Maple Street, LA, California"
現在讓我們執行讀取器從同一個檔案讀取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener" address: "212, Maple Street, LA, California"
所以,這裡沒有什麼新東西,我們只是根據 Protobuf 定義編寫了一個簡單的字串,並且讀取器能夠讀取該字串。
但是現在,假設我們要從我們的 Protobuf 定義中刪除字串'address'。因此,定義將如下所示:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
}
我們還將更新我們的寫入器如下:
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.build();
在編譯之前,將上一次編譯生成的 JAR 檔案重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: name: "Silver Screener"
現在讓我們執行讀取器從同一個檔案讀取,但使用舊的 JAR 檔案:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output Reading from file theater_protobuf_output name: "Silver Screener" address:
從輸出的最後一行可以看出,舊的讀取器將“address”的預設值設定為。它顯示了“新寫入器 - 舊讀取器”組合是如何工作的。
避免重複使用欄位的序號
在某些情況下,可能會錯誤地更新欄位的“序號”。這可能會導致問題,因為序號對於 Protobuf 理解和反序列化資料至關重要。一些舊的讀取器可能依賴於此序號來反序列化資料。因此,建議你:
不要更改欄位的序號
不要重複使用已刪除欄位的序號。
讓我們透過交換欄位標籤來看一下實際情況。
繼續我們的劇院示例,假設我們的proto檔案中只有兩個標籤。以下是我們需要用來指示 Protobuf 的語法:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
string address = 2;
}
要使用 Protobuf,我們現在必須使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:
protoc --java_out=java/src/main/java proto_files\theater.proto
上述命令應該建立所需的檔案,現在我們可以在 Java 程式碼中使用它。首先,我們將建立一個寫入器來寫入劇院資訊:
TheaterWriter.java
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setAddress("212, Maple Street, LA, California")
.build();
String filename = "theater_protobuf_output";
System.out.println("Saving theater information to file: " + filename);
try(FileOutputStream output = new FileOutputStream(filename)){
theater.writeTo(output);
}
System.out.println("Saved theater information with following data to disk: \n" + theater);
}
}
接下來,我們將有一個讀取器來讀取劇院資訊:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
public class TheaterReader{
public static void main(String[] args) throws IOException {
Builder theaterBuilder = Theater.newBuilder();
String filename = "theater_protobuf_output";
System.out.println("Reading from file " + filename);
try(FileInputStream input = new FileInputStream(filename)) {
Theater theater = theaterBuilder.mergeFrom(input).build();
System.out.println(theater);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: name: "Silver Screener" address: "212, Maple Street, LA, California"
接下來,讓我們執行讀取器從同一個檔案讀取:
java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener" address: "212, Maple Street, LA, California"
在這裡,我們只是根據 Protobuf 定義編寫了簡單的字串,並且讀取器能夠讀取字串。但是現在,讓我們在 Protobuf 定義中交換序號,使其像這樣:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 2;
string address = 1;
}
在編譯之前,將上一次編譯生成的 JAR 檔案重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: address: "212, Maple Street, LA, California" name: "Silver Screener"
現在讓我們執行讀取器從同一個檔案讀取,但使用舊的 JAR 檔案:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "212, Maple Street, LA, California" address: "Silver Screener"
從輸出可以看出,舊的讀取器交換了address和name。這表明更新序號以及“新寫入器-舊讀取器”的組合無法按預期工作。
更重要的是,這裡我們有兩個字串,這就是為什麼我們可以看到資料。如果我們使用不同的資料型別,例如int32、布林值、對映等,Protobuf 將放棄並將其視為未知欄位。
因此,務必不要更改欄位的序號或重複使用已刪除欄位的序號。
更改欄位型別
在某些情況下,我們需要更新屬性/欄位的型別。Protobuf 對此有一些相容性規則。並非所有型別都可以轉換為其他型別。需要注意一些基本型別:
如果位元組是 UTF-8,則字串和位元組是相容的。這是因為字串無論如何都會被 Protobuf 編碼/解碼為 UTF-8。
就值而言,列舉與int32和int64相容,但是客戶端可能無法按預期反序列化。
int32、int64(也包括無符號)以及布林值是相容的,因此可以互換。類似於語言中的強制型別轉換方式,可能會截斷多餘的字元。
但是更改型別時需要非常小心。讓我們透過將int64轉換為int32的錯誤示例來看一下實際情況。
繼續我們的劇院示例,假設我們的proto檔案中只有兩個標籤。以下是我們需要用來指示 Protobuf 的語法:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
int64 total_capacity = 2;
}
要使用 Protobuf,我們現在必須使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:
protoc --java_out=java/src/main/java proto_files\theater.proto
上述命令應該建立所需的檔案,現在我們可以在 Java 程式碼中使用它。首先,我們將建立一個寫入器來寫入劇院資訊:
TheaterWriter.java
package com.tutorialspoint.theater;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
public class TheaterWriter{
public static void main(String[] args) throws IOException {
Theater theater = Theater.newBuilder()
.setName("Silver Screener")
.setTotalCapacity(2300000000L)
.build();
String filename = "theater_protobuf_output";
System.out.println("Saving theater information to file: " + filename);
try(FileOutputStream output = new FileOutputStream(filename)){
theater.writeTo(output);
}
System.out.println("Saved theater information with following data to disk: \n" + theater);
}
}
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Saving theater information to file: theater_protobuf_output Saved theater information with following data to disk: name: "Silver Screener" total_capacity: 2300000000
假設我們對讀取器使用不同版本的proto檔案:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 1;
int64 total_capacity = 2;
}
接下來,我們將有一個讀取器來讀取劇院資訊:
TheaterReader.java
package com.tutorialspoint.theater;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
public class TheaterReader{
public static void main(String[] args) throws IOException {
Builder theaterBuilder = Theater.newBuilder();
String filename = "theater_protobuf_output";
System.out.println("Reading from file " + filename);
try(FileInputStream input = new FileInputStream(filename)) {
Theater theater = theaterBuilder.mergeFrom(input).build();
System.out.println(theater);
System.out.println("Unknwon fields: " + theater.getUnknownFields());
}
}
}
現在讓我們執行讀取器從同一個檔案讀取:
java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader Reading from file theater_protobuf_output name: "Silver Screener" address: "212, Maple Street, LA, California"
所以,這裡沒有什麼新東西,我們只是根據 Protobuf 定義編寫了簡單的字串,並且讀取器能夠讀取字串。但是現在,讓我們在 Protobuf 定義中交換序號,使其像這樣:
theater.proto
syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
string name = 2;
int32 total_capacity = 2;
}
在編譯之前,將上一次編譯生成的 JAR 檔案重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。
現在,編譯後,讓我們先執行寫入器:
> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter Reading from file theater_protobuf_output address: "Silver Screener" total_capcity: -1994967296
從輸出可以看出,舊的讀取器將數字從int64轉換了,但是給定的int32沒有足夠的空間來容納資料,它繞回到負數。這種環繞是 Java 特有的,與 Protobuf 無關。
因此,我們需要從int32升級到int64,而不是反過來。如果我們仍然想將int64轉換為int32,我們需要確保值實際上可以儲存在 31 位中(1 位用於符號位)。