- 官网地址:https://protobuf.dev/overview/
- GitHub 项目地址:https://github.com/protocolbuffers/protobuf
Protocol Buffers(简称 Protobuf)是 Google 开发的一种高效、跨平台的数据序列化协议,专为结构化数据的存储和通信设计。
它通过简洁的接口定义语言(IDL)描述数据结构,并生成高效的序列化代码,广泛应用于微服务通信(如 gRPC)、大数据存储等场景。
Protocol Buffers 的核心优势:
在 .proto
文件中定义数据结构和接口(示例:hello.proto):
syntax = "proto3"; // 指定使用 proto3 语法
option java_package="com.example"; // 生成文件所在路径及包名。
service serviceName { // 定义服务,在这个服务中需要有一个方法,这个方法可以接受客户端的参数,再返回服务端的响应。
rpc GetUser (UserRequest) returns (UserResponse);
}
message UserRequest { // 定义消息类型,命名规范:使⽤驼峰命名法,⾸字⺟⼤写。
string name = 1; // 字段类型 + 名称 + 标签号(不可重复)
int32 age = 2;
repeated string hobbies = 3; // repeated 表示返回多个数据,可理解为Java List
}
.proto
文件命名应该使⽤全⼩写字⺟命名,多个字⺟之间⽤ _ 连接。 例如:lower_snake_case.proto在利用 Protobuf 进行网络数据传输时,确保通信双方拥有一致的 .proto 文件至关重要。缺少了相应的 .proto 文件,通信任何一方都无法生成必要的工具函数代码,进而无法解析接收到的消息数据。
与 JSON 这种文本格式不同,后者即便在没有 JSON.parse 反序列化函数的情况下,人们仍能大致推断出消息内容。相比之下,Protobuf 序列化后的数据是二进制字节流,它并不适合人类阅读,且必须通过特定的反序列化函数才能正确解读数据。
Protobuf 的这种设计在提高数据安全性方面具有优势,因为缺少 .proto 文件就无法解读数据内容。然而,这也意味着在通信双方之间需要维护一致的 .proto 文件,随着项目的扩展,这可能会带来额外的维护成本。
reserved 2, 15 to 20; // 保留标签号
reserved "email"; // 保留字段名
格式 | 编码方式 | 可读性 | 体积 | 解析速度 |
---|---|---|---|---|
XML | 文本 | 高 | 大 | 慢 |
JSON | 文本 | 高 | 较大 | 较慢 |
Protobuf | 二进制 | 低 | 小 | 极快 |
protoc 是 Protocol Buffers 的核心编译器,用于将 .proto 文件编译为不同语言的代码(如 Java、C++、Python 等)。
(1)安装编译器 protoc
官网安装文档:https://protobuf.dev/installation/
在 https://github.com/protocolbuffers/protobuf/releases 上下载对应的版本,然后配置环境变量:
# 配置环境变量
cat > ~/.zshrc <<EOF
# Protocol Buffers
export PATH="$PATH:/Users/zs/App/env/protoc-25/bin"
EOF
# 验证
protoc --version
(2)IDEA配置protobuf插件
IntelliJ IDEA
⇒ Settings
⇒ Plugins
⇒ MarketPlace
,输入 Protocol Buffers
,点击 Install
(3)基础命令结构
protoc [OPTIONS] PROTO_FILES
PROTO_FILES # 待编译的 .proto 文件路径(如 src/main/proto/hello.proto)
OPTIONS: # 控制代码生成和编译行为的参数
--<lang>_out=OUT_DIR # 指定生成代码的语言和输出目录(如 --java_out、--python_out)(--java_out=src/main/java)
--plugin=EXECUTABLE # 指定自定义插件(如 gRPC 插件) (--plugin=protoc-gen-grpc-java=/path/to/plugin)
--grpc-<lang>_out # 生成 gRPC 服务代码(需安装对应语言的 gRPC 插件) (--grpc-java_out=src/main/java)
-IPATH, --proto_path=PATH # 指定 .proto 文件的搜索路径(可多次使用) (-I src/main/proto -I ../shared/proto)
--descriptor_set_out=FILE # 生成描述符文件(包含所有编译的 .proto 信息) (--descriptor_set_out=my_protos.desc)
--version # 显示 protoc 版本 ( protoc --version)
-h, --help # 显示帮助信息 (protoc --help)
--encode=MESSAGE # 将文本消息编码为二进制(需指定 .proto) (protoc --encode=MyMessage my.proto < input.txt)
--decode=MESSAGE # 将二进制消息解码为文本 (protoc --decode=MyMessage) my.proto < input.bin
示例:
# 场景 1:生成 Java 代码
protoc \
--proto_path=src/main/proto \ # .proto 文件搜索路径
--java_out=src/main/java \ # Java 代码输出目录
src/main/proto/hello.proto # 待编译的 proto 文件
# 场景 2:生成 Java gRPC 代码
# 需提前安装 grpc-java 插件(protoc-gen-grpc-java)
protoc \
--proto_path=src/main/proto \
--java_out=src/main/java \
--grpc-java_out=src/main/java \ # 生成 gRPC 服务代码
--plugin=protoc-gen-grpc-java=/path/to/protoc-gen-grpc-java \ # 显式指定插件路径
src/main/proto/hello.proto
# 场景 3:多文件批量编译
protoc \
-I src/main/proto \
-I ../shared/protos \ # 多路径导入
--java_out=out/java \
src/main/proto/*.proto \ # 编译所有 proto 文件
../shared/protos/utils/*.proto
# 场景 4:生成描述符文件(Descriptor Set)
protoc \
--proto_path=src/main/proto \
--descriptor_set_out=my_protos.desc \ # 输出描述符文件
--include_imports \ # 包含所有依赖
src/main/proto/hello.proto
Java + gRpc:开发一个通讯录服务,根据联系人名字返回其电话号码。
需要注意的是:grpc服务调用底层已经用protobuf实现了序列化与反序列化,故无需手动序列化。
contract
contract
项目添加三个模块:grpc-api、grpc-service、grpc-clientgrpc-api
模块修改内容:
syntax = "proto3";
option java_multiple_files = false;
option java_package = "com.example";
option java_outer_classname = "ContactProto"
message ContractRequest{
string name = 1;
}
message ContractResponse{
string tel = 1;
}
service ContractService{
rpc query(ContractRequest) returns (ContractResponse);
}
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-netty-shadedartifactId>
<version>1.70.0version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-protobufartifactId>
<version>1.70.0version>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-stubartifactId>
<version>1.70.0version>
dependency>
<dependency>
<groupId>org.apache.tomcatgroupId>
<artifactId>annotations-apiartifactId>
<version>6.0.53version>
<scope>providedscope>
dependency>
<build>
<extensions>
<extension>
<groupId>kr.motd.mavengroupId>
<artifactId>os-maven-pluginartifactId>
<version>1.7.1version>
extension>
extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.pluginsgroupId>
<artifactId>protobuf-maven-pluginartifactId>
<version>0.6.1version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.25.5:exe:${os.detected.classifier}protocArtifact>
<pluginId>grpc-javapluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.70.0:exe:${os.detected.classifier}pluginArtifact>
<outputDirectory>${basedir}/src/main/javaoutputDirectory>
<clearOutputDirectory>falseclearOutputDirectory>
configuration>
<executions>
<execution>
<goals>
<goal>compilegoal>
<goal>compile-customgoal>
goals>
execution>
executions>
plugin>
plugins>
build>
${os.detected.classifier} 可能会飘红:Cannot resolve symbol 'os. detected. classifier'
,点击 Reimport
即可(base) zs@Mac contract % cd grpc-api
(base) zs@Mac grpc-api % mvn protobuf:compile protobuf:compile-custom
生成的代码在 contract/grpc-api/src/main/java/com/example
类名 | 作用 |
---|---|
ContractServiceGrpc | 入口类,包含服务描述符、方法定义和 Stub 工厂方法(如 newStub) |
ContractServiceImplBase | 服务端基类,需要继承并实现 query 方法的具体逻辑 |
ContractServiceBlockingStub | 客户端同步调用存根,直接阻塞等待响应(如 query 方法调用) |
ContractServiceFutureStub | 客户端异步调用存根,返回 ListenableFuture 对象处理响应 |
ContractServiceStub | 客户端异步流式存根,使用 StreamObserver 处理请求和响应 |
ServiceDescriptor | 服务的元数据描述,包含方法名、请求响应类型等信息 |
方法 | 功能 |
---|---|
getQueryMethod() | 返回 query 方法的描述符(请求类型 ContractRequest,响应类型 ContractResponse)。 |
newBlockingStub(Channel) | 创建同步客户端存根,用于阻塞式调用服务端方法。 |
bindService(AsyncService) | 将服务端实现类绑定到 gRPC 服务器,生成 ServerServiceDefinition。 |
<dependency>
<groupId>com.examplegroupId>
<artifactId>grpc-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
package com.example.service;
import com.example.ContactProto;
import com.example.ContractServiceGrpc;
import io.grpc.stub.StreamObserver;
public class ContractServiceImpl extends ContractServiceGrpc.ContractServiceImplBase {
@Override
public void query(ContactProto.ContractRequest request, StreamObserver<ContactProto.ContractResponse> responseObserver) {
//1.业务处理
System.out.println("Receive client data: " + request.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//2.封装响应
ContactProto.ContractResponse response = ContactProto.ContractResponse.newBuilder().setTel("+100 110120119").build();
//3.响应
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
package com.example;
import com.example.service.ContractServiceImpl;
import io.grpc.ServerBuilder;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
ServerBuilder.forPort(9000)
.addService(new ContractServiceImpl())
.build()
.start()
.awaitTermination();
}
}
<dependency>
<groupId>com.examplegroupId>
<artifactId>grpc-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
package com.example;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import java.util.Iterator;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
//1.创建通信管道
ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
try{
//2.获取代理对象 stub
ContractServiceGrpc.ContractServiceBlockingStub contractServiceBlockingStub = ContractServiceGrpc.newBlockingStub(managedChannel);
//3.创建请求对象
ContactProto.ContractRequest contractRequest = ContactProto.ContractRequest.newBuilder().setName("Mary").build();
//4.grpc调用
ContactProto.ContractResponse contractResponse = contractServiceBlockingStub.query(contractRequest);
//5.业务处理
System.out.println("unary rpc call: " + contractResponse.getTel());
} catch (Exception e){
System.out.println(e.getMessage());;
} finally {
managedChannel.shutdown();
}
}
}
适用场景:
局限性: