Protocol Buffers (Protobuf) 是 Google 开发的一种结构化数据二进制序列化格式,用于服务之间的高效信息交换。该格式要求在.proto文件中预先定义模式,从而生成不同语言的代码。根据Google官方文档,Protobuf确保消息大小比JSON小3-10倍。Protobuf用于gRPC、Google Maps和数千个内部服务。
核心要点
Protocol Buffers 是 Google 开发的一种结构化数据序列化机制,类似于 JSON 和 XML,但有根本不同:数据以二进制格式编码。这意味着Protobuf消息无法用肉眼直接阅读,但占用更少空间,处理速度也比文本形式更快。
Protobuf 是在 Google 内部创建的,用于解决服务之间数据交换时的性能问题。2008年,该技术成为开源项目,支持多种语言:C++、Java、Python、Go、JavaScript、Kotlin、Swift、Dart等。2016年发布的proto3版本简化了语法并增加了对更多语言的支持。
Protobuf 和 JSON 之间的关键差异是在开始数据交换之前需要定义模式。模式文件(.proto)描述消息的结构:字段、类型和唯一字段编号。protoc编译器从这个模式生成目标语言的类,用于序列化和反序列化。
Protobuf架构包括三个关键组件:模式定义语言(.proto)、protoc编译器和特定语言的运行时库。开发者在.proto文件中描述数据结构,运行编译后获得处理这些数据的已准备好的类。
Protobuf消息中的每个字段都有唯一编号(field number)—这不是序号,而是二进制格式中用于识别字段的标记。1到15号用一个字节编码,16到2047号用两个字节。因此,使用频率高的重要字段应编号1到15以优化大小。
Protobuf支持广泛的数据类型:标量类(int32, int64, float, double, bool, string, bytes)、枚举(enum)、复合(message)和特殊(oneof, map)。每种类型都有特定的二进制表示,优化于相应的使用场景。
| .proto类型 | C++类型 | Java/Kotlin | 说明 |
|---|---|---|---|
| double | double | double | 64位浮点数 |
| float | float | float | 32位浮点数 |
| int32 | int32 | int | 32位,variable-length encoding |
| int64 | int64 | long | 64位,variable-length encoding |
| string | string | String | UTF-8字符串 |
| bytes | string | ByteString | 任意字节 |
| bool | bool | boolean | true / false |
Protobuf相比于文本格式有三个关键优势:消息大小、序列化速度和严格类型。在高负载系统和流量有限的移动应用中,这些优势变得至关重要。
Protobuf的二进制表示使用variable-length encoding(Varint)来处理数字:小数字占用1个字节,大数字最多占用10个字节。这可以高效编码标识符、标志位和计数器,而这些在JSON或XML中作为文本字符串会占用数十个字节。例如,数字150在Protobuf中占用2个字节,在JSON中占用3个字节(作为文本“150”),在XML中占用3个字节加标签。
额外的优势是向后兼容。在模式中添加新字段不会破坏旧客户端:它们只是忽略未知字段。删除字段只需要保留其编号以避免未来冲突。
.proto模式使用专门语言描述数据结构。文件从指定语法(proto3)、包名和导入依赖开始。每个消息使用message关键字定义,带有字段,每个字段有类型、名称和唯一编号。
良好模式的规则:经常使用的字段使用1到15号,有意义的名称,相关字段分组到单独的message中,可能是多个变量之一的字段使用oneof。
syntax = "proto3";
package mobileapp;
message User {
string user_id = 1;
string name = 2;
string email = 3;
int32 age = 4;
UserRole role = 5;
repeated string tags = 6;
map<string, string> metadata = 7;
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_USER = 1;
USER_ROLE_ADMIN = 2;
USER_ROLE_MODERATOR = 3;
}
消息可以嵌套:Profile类型的profile字段将包含用户数据。嵌套支持可以描述复杂的层级结构而无需重复定义。
syntax = "proto3";
package mobileapp;
message Order {
string order_id = 1;
repeated OrderItem items = 2;
double total_price = 3;
PaymentInfo payment = 4;
}
message OrderItem {
string product_id = 1;
string title = 2;
int32 quantity = 3;
double price = 4;
}
message PaymentInfo {
string method = 1;
string transaction_id = 2;
double amount = 3;
}
protoc编译器将.proto文件转换为目标语言的源代码。对于Kotlin/Java使用--java_out参数,Swift使用--swift_out(通过Apple Swift Protobuf插件),Dart使用--dart_out。生成的类包含构建消息的builder方法和序列化/反序列化方法。
在Android上,通过Gradle中的com.google.protobuf插件(版本0.9+)来使用Protobuf。添加插件并指定.proto文件后,编译过程会自动生成可用于应用程序的Kotlin类。
import com.google.protobuf.kotlin.toByteString
import com.example.mobileapp.UserOuterClass.User
fun createUser(): User {
return User.newBuilder()
.setUserId("usr_001")
.setName("IT Sectr")
.setEmail("team@itsectr.com")
.setAge(5)
.setRole(UserOuterClass.UserRole.USER_ROLE_ADMIN)
.addTags("mobile")
.addTags("backend")
.build()
}
fun serializeAndDeserialize(user: User): User {
// 序列化为二进制格式
val bytes = user.toByteArray()
// 从二进制格式反序列化
return User.parseFrom(bytes)
}
Protobuf在微服务架构和移动应用中特别高效。考虑一个典型场景:移动应用通过gRPC从服务器接收产品列表。Protobuf消息包含产品信息,包括标识符、名称、价格和分类。二进制格式将响应大小减少5-8倍。
服务端Protobuf使用gRPC服务的示例。服务声明GetProducts RPC方法,接收带分页参数的请求并返回产品列表。Kotlin实现使用生成的类来处理请求和响应。
import com.example.mobileapp.ProductServiceGrpcKt
import com.example.mobileapp.ProductOuterClass.Product
import com.example.mobileapp.ProductOuterClass.GetProductsRequest
import com.example.mobileapp.ProductOuterClass.GetProductsResponse
class ProductService : ProductServiceGrpcKt.ProductServiceCoroutineImplBase() {
override suspend fun getProducts(
request: GetProductsRequest
): GetProductsResponse {
val products = fetchProductsFromDb(
page = request.page,
limit = request.limit
)
return GetProductsResponse.newBuilder()
.addAllProducts(products)
.setTotalCount(products.size)
.build()
}
}
配置Protobuf通过Gradle插件com.google.protobuf在Android项目中完成。插件在编译时自动启动protoc编译器,从.proto文件生成Kotlin类。需要在项目级别的build.gradle中添加插件并在应用模块中应用。
配置Gradle后,.proto文件放置在src/main/proto目录中。protoc编译器在每次编译时处理它们,生成可用于应用代码的Kotlin类。正确指定protobuf和protoc版本很重要,以避免与项目其他库产生依赖冲突。
对于iOS项目,通过CocoaPods或Swift Package Manager连接Protobuf。Swift Protobuf插件自动生成符合Codable协议的Swift结构。对于Flutter的Dart项目,使用protobuf包,编译通过dart run protoc_plugin完成。
常见问题
Protobuf是带必要模式的二进制格式,比JSON紧凑3-10倍。JSON是文本格式,人可读,不需要模式。Protobuf序列化和反序列化更快,但需要编译.proto文件。JSON更容易调试,不需要预先配置。
从protobuf的GitHub发行版中为您的平台下载protoc。macOS通过brew install protobuf安装。Windows下载zip压缩包并将protoc.exe添加到PATH。Android/Kotlin使用com.google.protobuf Gradle插件,它在构建时自动启动编译。
gRPC是Google的高性能RPC框架,使用Protobuf作为接口语言(IDL)和序列化格式。gRPC在.proto文件中定义服务和RPC方法,生成客户端和服务端代码,支持流式传输和二进制协议。
Google官方支持:C++、Java、Kotlin、Python、Go、Ruby、C#、PHP、JavaScript、Objective-C、Swift和Dart。社区开发了对Rust、TypeScript、Scala、Lua等语言的支持。移动开发可使用Kotlin/Java(Android)和Swift/Objective-C(iOS)。
Protobuf通过以下规则支持向后兼容:不要修改字段编号,不要删除字段(使用reserved),使用新编号添加新字段。旧客户端忽略未知字段,新客户端为缺失的旧字段获取默认值。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。