Protocol Buffer 使用指北

陳敏傑發表於2019-02-24

最近在負責特徵平臺服務的開發,最初版本的特徵資料存取是使用 JSON String,當時的考量是快速迭代上線,JSON 是個比較簡單常用的資料交換格式.這樣做在簡單資料結構且對效能要求不高的時候是可行的,但是隨著這個服務的呼叫量增大,對效能也有要求,這種做法就不太可行,碰到主要有以下問題:

  • JSON String 在 parse 的時候很慢, 在呼叫量大的時候經常需要花費幾十毫秒的時間,從而導致呼叫超時.
  • JSON 結構比較佔用儲存空間,沒有進行任何壓縮,相對來說網路傳輸也會更耗時.
  • GC 問題

中介軟體團隊建議使用Protocol Buffer,調研之後決定用 Protocol Buffer 來優化一下服務,下面簡單介紹下它的使用方法,主要包含三個步驟:

  1. .proto 檔案中定義資料格式
  2. 根據 .proto 檔案生成類檔案
  3. 使用

1.定義資料格式

新建一個以 .proto 結尾的檔案,如新建 mq_message.proto:

// 指定使用哪個版本的語法,需要在第一行指定
syntax = "proto3";

package example;

// 指定 java_package, 不指定就使用 package.生成的類會放到該package下
option java_package = "com.example.example";

// 生成的類名
option java_outer_classname = "MQMessageProtos";

message MQMessage {
  // required 修飾的欄位必須提供,否則 build 會拋 RuntimeException, parse 會拋 IOException. 後續擴充套件時 required 欄位不可修改

  // 宣告一個列舉型別
  enum OperationType {
    UPDATE = 0;
    DELETE = 1;
  }

  // 宣告一個列舉型別的欄位,等號後面的數字是標識數字
  OperationType operationType = 1;

  // 宣告一個 string 型別的欄位
  string name = 2;

  // 宣告一個 Item 的 List
  repeated Item items = 3;

}

message Item {
  // 宣告一個 string 型別的欄位
  string key = 1;

  // 宣告一個 map 型別欄位
  map<string, string> fields = 2;
}

複製程式碼
  1. 首先指定語法版本, proto2proto3 有一些差別,詳細的可以參考官方文件(可能需要梯子,proto2, proto3),本文中使用 proto3.
  2. package 和 java_package 指定生成的Java類在哪個 package 下,避免命名衝突.
  3. java_outer_classname 指定生成的類名,如果不指定則會根據檔名生成,如: my_proto.proto 則會生成 MyProto 類.
  4. message 是用於資料格式定義.
    • 一個 .proto 檔案中可以定義多個 message
    • message 中定義的欄位支援 string、byte、bool、map、enum、數字型別和使用者自定義的 message
    • 定義欄位後面需要指定唯一的標識數字,這些數字用於識別二進位制格式 message 中的欄位,一旦開始使用這個 message,那麼標識數字就不能改變.(數字1-15會用一個位元組去編碼,大於15的數字會用更多位元組去編碼,所以儘量把小的數字留個最常出現的欄位.指定欄位的標識數字時不用按照順序來.)
    • 如果需要定義 List,則在欄位前加repeated即可.
    • 如果已經使用過該 message 生成的類後,想要增加欄位直接新增即可.當新增欄位的類解析老資料時,會將新欄位置為預設值.當舊的類解析新資料時會忽視掉新增欄位.

2.根據 .proto 檔案生成類檔案

定義好 .proto 檔案後使用 protocol buffer 編譯器編譯 .proto 檔案即可. 需要在本地安裝一下 protocol buffer 編譯器,Mac os 直接使用 brew 安裝即可: brew install protobuf.其他系統可參照: 官方文件 安裝好編譯器後執行編譯命令:

protoc --java_out=./ ./mq_message.proto
複製程式碼

--java_out 指定編譯後生成的檔案所在的目錄,最後指定你要編譯的檔案.

3.使用

下面是根據上面的mq_message.proto檔案生成的類的使用例子:

public class TestPB {
    public static void main(String[] args) {
        MQMessageProtos.Item item1 = MQMessageProtos.Item.newBuilder()
                .setKey("1")
                .putFields("field1", "value1")
                .putFields("field2", "value2")
                .build();
        MQMessageProtos.Item item2 = MQMessageProtos.Item.newBuilder()
                .setKey("2")
                .putFields("field3", "value3")
                .putFields("field4", "value4")
                .build();
        MQMessageProtos.MQMessage mqMessage = MQMessageProtos.MQMessage.newBuilder()
                .setName("testName")
                .setOperationType(MQMessageProtos.MQMessage.OperationType.UPDATE)
                .addItems(item1)
                .addItems(item2)
                .build();

        byte[] byteArray = mqMessage.toByteArray();

        try {
            MQMessageProtos.MQMessage message = MQMessageProtos.MQMessage.parseFrom(byteArray);
            System.out.println(message.getName());
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

最後歡迎關注我的個人部落格:陳敏傑

相關文章