最近在負責特徵平臺服務的開發,最初版本的特徵資料存取是使用 JSON String,當時的考量是快速迭代上線,JSON 是個比較簡單常用的資料交換格式.這樣做在簡單資料結構且對效能要求不高的時候是可行的,但是隨著這個服務的呼叫量增大,對效能也有要求,這種做法就不太可行,碰到主要有以下問題:
- JSON String 在 parse 的時候很慢, 在呼叫量大的時候經常需要花費幾十毫秒的時間,從而導致呼叫超時.
- JSON 結構比較佔用儲存空間,沒有進行任何壓縮,相對來說網路傳輸也會更耗時.
- GC 問題
中介軟體團隊建議使用Protocol Buffer,調研之後決定用 Protocol Buffer 來優化一下服務,下面簡單介紹下它的使用方法,主要包含三個步驟:
- 在 .proto 檔案中定義資料格式
- 根據 .proto 檔案生成類檔案
- 使用
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;
}
複製程式碼
- 首先指定語法版本, proto2 和 proto3 有一些差別,詳細的可以參考官方文件(可能需要梯子,proto2, proto3),本文中使用 proto3.
- package 和 java_package 指定生成的Java類在哪個 package 下,避免命名衝突.
- java_outer_classname 指定生成的類名,如果不指定則會根據檔名生成,如: my_proto.proto 則會生成 MyProto 類.
- 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();
}
}
}
複製程式碼
最後歡迎關注我的個人部落格:陳敏傑