Protobuf
Protocol Buffers(Protobuf)是 Google 开发的语言中立、平台中立的二进制序列化格式,是 gRPC 的默认序列化协议。与 JSON/XML 相比,体积小 3~10 倍,解析速度快 20~100 倍。
与其他格式对比
| 特性 | Protobuf | JSON | XML |
|---|
| 编码格式 | 二进制 | 文本 | 文本 |
| 体积 | 小(无字段名) | 中 | 大 |
| 解析速度 | 极快 | 中 | 慢 |
| 可读性 | 差(需工具) | 好 | 好 |
| 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 类型 | 说明 |
|---|
int32 | int | 负数效率低,用 sint32 |
int64 | long | 同上 |
sint32 | int | ZigZag 编码,适合负数 |
uint32 | int | 无符号 |
fixed32 | int | 始终 4 字节,适合大正整数 |
float | float | 32 位浮点 |
double | double | 64 位浮点 |
bool | boolean | |
string | String | UTF-8 |
bytes | ByteString | 任意字节序列 |
repeated | List | 数组 |
map<K,V> | Map | 键值对 |
oneof | — | 多字段互斥 |
编码原理
Protobuf 使用 Tag-Length-Value(TLV) 二进制编码,字段名不传输,仅传字段编号:
field_tag = (field_number << 3) | wire_type
| Wire Type | 含义 | 对应类型 |
|---|
| 0 | Varint | int32/64, bool, enum |
| 1 | 64-bit | fixed64, double |
| 2 | Length-delimited | string, bytes, message, repeated |
| 5 | 32-bit | fixed32, 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());
}
相关链接