Protobuf

Protocol Buffers(Protobuf)是 Google 开发的语言中立、平台中立的二进制序列化格式,是 gRPC 的默认序列化协议。与 JSON/XML 相比,体积小 3~10 倍,解析速度快 20~100 倍。

与其他格式对比

特性ProtobufJSONXML
编码格式二进制文本文本
体积小(无字段名)
解析速度极快
可读性差(需工具)
Schema必须(.proto)可选可选(XSD)
向前/后兼容原生支持手动处理手动处理
跨语言

.proto 文件语法(proto3)

syntax = "proto3";
 
package order;
option java_package = "com.example.order.proto";
option java_outer_classname = "OrderProto";
 
// 枚举(第一个值必须为 0)
enum OrderStatus {
  UNKNOWN   = 0;
  PENDING   = 1;
  PAID      = 2;
  CANCELLED = 3;
}
 
// 消息
message Order {
  int64        id      = 1;
  string       user_id = 2;
  OrderStatus  status  = 3;
  double       amount  = 4;
  repeated Item items  = 5;         // 数组
  map<string, string> meta = 6;     // map
  optional string remark   = 7;     // 可选字段
}
 
message Item {
  int64  sku_id   = 1;
  int32  quantity = 2;
  double price    = 3;
}
 
// Service(配合 gRPC 使用)
service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
}
message GetOrderRequest { int64 id = 1; }

字段类型速查

Proto 类型Java 类型说明
int32int负数效率低,用 sint32
int64long同上
sint32intZigZag 编码,适合负数
uint32int无符号
fixed32int始终 4 字节,适合大正整数
floatfloat32 位浮点
doubledouble64 位浮点
boolboolean
stringStringUTF-8
bytesByteString任意字节序列
repeatedList数组
map<K,V>Map键值对
oneof多字段互斥

编码原理

Protobuf 使用 Tag-Length-Value(TLV) 二进制编码,字段名不传输,仅传字段编号:

field_tag = (field_number << 3) | wire_type
Wire Type含义对应类型
0Varintint32/64, bool, enum
164-bitfixed64, double
2Length-delimitedstring, bytes, message, repeated
532-bitfixed32, float

Varint 编码:每字节最高位为续位标志,低 7 位存数据,小数字占用更少字节(如数字 1 只占 1 字节)。

代码生成

# 生成 Java 代码
protoc --java_out=src/main/java \
       --proto_path=src/main/proto \
       order.proto
 
# 生成 Java + gRPC 代码(需 protoc-gen-grpc-java 插件)
protoc --java_out=src/main/java \
       --grpc-java_out=src/main/java \
       --proto_path=src/main/proto \
       order.proto

Maven 插件(推荐)

<plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
        <protocArtifact>
            com.google.protobuf:protoc:3.x.x:exe:${os.detected.classifier}
        </protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>
            io.grpc:protoc-gen-grpc-java:1.x.x:exe:${os.detected.classifier}
        </pluginArtifact>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>compile-custom</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Java API 使用

// 构建消息
Order order = Order.newBuilder()
    .setId(1001L)
    .setUserId("u_001")
    .setStatus(OrderStatus.PAID)
    .setAmount(99.9)
    .addItems(Item.newBuilder()
        .setSkuId(501L)
        .setQuantity(2)
        .setPrice(49.95)
        .build())
    .putMeta("channel", "app")
    .build();
 
// 序列化为字节数组
byte[] bytes = order.toByteArray();
 
// 反序列化
Order parsed = Order.parseFrom(bytes);

向前/后兼容规则

操作兼容性
添加新字段(新编号)✅ 兼容(旧客户端忽略未知字段)
删除字段✅ 兼容(需保留编号,标 reserved
修改字段编号❌ 不兼容
修改字段类型❌ 不兼容(少数例外)
修改字段名✅ 兼容(编码不含名字)
// 安全删除字段后,保留编号防止复用
message Order {
  reserved 4, 8 to 10;        // 保留编号
  reserved "old_field_name";  // 保留字段名
}

oneof(互斥字段)

message PaymentInfo {
  oneof method {
    string alipay_id = 1;
    string wechat_id = 2;
    CreditCard card  = 3;
  }
}
if (info.hasAlipayId()) {
    System.out.println(info.getAlipayId());
} else if (info.hasCard()) {
    System.out.println(info.getCard().getNumber());
}

相关链接