gRPC 详解¶
一、什么是 gRPC?¶
gRPC 是 Google 于 2015 年开源的高性能 RPC 框架,全称 Google Remote Procedure Call。
它基于以下两项核心技术构建: - HTTP/2:作为传输协议,支持多路复用、双向流、头部压缩 - Protocol Buffers(Protobuf):作为接口定义语言(IDL)和序列化格式
官网:https://grpc.io
二、核心组成¶
2.1 Protocol Buffers(Protobuf)¶
Protobuf 是 gRPC 的"契约文件",用 .proto 文件定义服务接口和数据结构。
// user.proto
syntax = "proto3";
package user;
// 定义服务
service UserService {
rpc GetUser (GetUserRequest) returns (UserResponse);
rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}
// 定义请求/响应消息
message GetUserRequest {
int64 id = 1;
}
message UserResponse {
int64 id = 1;
string name = 2;
string email = 3;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}
字段编号的作用:Protobuf 序列化时用字段编号(而非字段名)标识字段,这是它体积小、解析快的关键。
2.2 HTTP/2 传输¶
相比 HTTP/1.1,HTTP/2 带来了显著提升:
| 特性 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 连接复用 | ❌ 每次请求新建连接 | ✅ 单连接多路复用 |
| 头部压缩 | ❌ 每次全量发送 | ✅ HPACK 压缩 |
| 双向流 | ❌ 不支持 | ✅ 支持 |
| 服务端推送 | ❌ 不支持 | ✅ 支持 |
三、四种通信模式¶
gRPC 支持四种调用方式,这是它比普通 HTTP 更强大的地方:
┌─────────────────────────────────────────────────────────┐
│ 1. 一元 RPC(Unary) │
│ Client ──请求──▶ Server ──响应──▶ Client │
│ (最常用,类似普通函数调用) │
├─────────────────────────────────────────────────────────┤
│ 2. 服务端流式(Server Streaming) │
│ Client ──请求──▶ Server ──流式响应──▶ Client │
│ (如:订阅实时数据、大文件下载) │
├─────────────────────────────────────────────────────────┤
│ 3. 客户端流式(Client Streaming) │
│ Client ──流式请求──▶ Server ──响应──▶ Client │
│ (如:上传大文件、批量写入) │
├─────────────────────────────────────────────────────────┤
│ 4. 双向流式(Bidirectional Streaming) │
│ Client ◀──流式──▶ Server │
│ (如:实时聊天、游戏对战) │
└─────────────────────────────────────────────────────────┘
对应 .proto 定义:
service ChatService {
// 1. 一元 RPC
rpc SendMessage (MessageRequest) returns (MessageResponse);
// 2. 服务端流式
rpc Subscribe (SubscribeRequest) returns (stream Event);
// 3. 客户端流式
rpc Upload (stream Chunk) returns (UploadResponse);
// 4. 双向流式
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
四、工作流程¶
┌──────────────────────────────────────────────────────────────┐
│ 开发阶段 │
│ 编写 .proto 文件 ──▶ protoc 编译器 ──▶ 生成各语言代码 │
│ (Java/Go/Python...) │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 运行阶段 │
│ │
│ Client Server │
│ ┌─────────┐ ┌─────────┐ │
│ │ 调用方法 │──▶ Stub(存根) │ 服务实现 │ │
│ └─────────┘ │ └─────────┘ │
│ │ 1. Protobuf 序列化 │
│ │ 2. HTTP/2 传输 │
│ │ 3. Protobuf 反序列化 │
│ ▼ │
│ Channel(连接管理) │
└──────────────────────────────────────────────────────────────┘
五、Java 中使用 gRPC¶
5.1 添加依赖(Maven)¶
<dependencies>
<!-- gRPC 核心 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.60.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.60.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.60.0</version>
</dependency>
</dependencies>
<build>
<extensions>
<!-- protobuf 编译插件 -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:1.60.0:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
</plugin>
</plugins>
</build>
5.2 编写 .proto 文件¶
// src/main/proto/user.proto
syntax = "proto3";
option java_package = "com.example.grpc";
option java_outer_classname = "UserProto";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (UserResponse);
}
message GetUserRequest {
int64 id = 1;
}
message UserResponse {
int64 id = 1;
string name = 2;
string email = 3;
}
5.3 实现服务端¶
// 继承自动生成的抽象类
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(GetUserRequest request,
StreamObserver<UserResponse> responseObserver) {
// 业务逻辑
UserResponse response = UserResponse.newBuilder()
.setId(request.getId())
.setName("张三")
.setEmail("[email protected]")
.build();
// 返回结果
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
// 启动服务器
public class GrpcServer {
public static void main(String[] args) throws Exception {
Server server = ServerBuilder.forPort(9090)
.addService(new UserServiceImpl())
.build()
.start();
System.out.println("gRPC 服务启动,端口:9090");
server.awaitTermination();
}
}
5.4 实现客户端¶
public class GrpcClient {
public static void main(String[] args) {
// 创建 Channel(连接)
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 9090)
.usePlaintext() // 开发环境不用 TLS
.build();
// 创建 Stub(存根,相当于代理对象)
UserServiceGrpc.UserServiceBlockingStub stub =
UserServiceGrpc.newBlockingStub(channel);
// 像调用本地方法一样调用远程服务
GetUserRequest request = GetUserRequest.newBuilder()
.setId(1L)
.build();
UserResponse response = stub.getUser(request);
System.out.println("用户名:" + response.getName());
channel.shutdown();
}
}
六、Spring Boot 集成 gRPC¶
实际项目中通常使用 grpc-spring-boot-starter 简化配置:
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>
服务端:
@GrpcService // 替代手动注册
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Autowired
private UserRepository userRepository;
@Override
public void getUser(GetUserRequest request,
StreamObserver<UserResponse> responseObserver) {
User user = userRepository.findById(request.getId());
UserResponse response = UserResponse.newBuilder()
.setId(user.getId())
.setName(user.getName())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
客户端:
@Service
public class UserClient {
@GrpcClient("user-service") // 对应配置文件中的服务名
private UserServiceGrpc.UserServiceBlockingStub userStub;
public UserResponse getUser(long id) {
return userStub.getUser(
GetUserRequest.newBuilder().setId(id).build()
);
}
}
配置文件:
# 服务端
grpc:
server:
port: 9090
# 客户端
grpc:
client:
user-service:
address: static://localhost:9090
negotiation-type: plaintext
七、gRPC vs REST 对比¶
| 对比项 | gRPC | REST |
|---|---|---|
| 协议 | HTTP/2 | HTTP/1.1 |
| 数据格式 | Protobuf(二进制) | JSON(文本) |
| 性能 | ✅ 高(体积小、解析快) | 相对低 |
| 流式支持 | ✅ 四种模式 | ❌ 有限支持 |
| 跨语言 | ✅ 自动生成代码 | ✅ 手动实现 |
| 可读性 | ❌ 二进制不可读 | ✅ JSON 可直接阅读 |
| 浏览器支持 | ❌ 需要 grpc-web | ✅ 原生支持 |
| 接口文档 | ✅ .proto 即文档 | 需要 Swagger 等工具 |
| 适用场景 | 内部微服务通信 | 对外 API、前后端通信 |
八、适用场景¶
✅ 推荐使用 gRPC 的场景: - 微服务内部高频调用,对性能要求高 - 需要双向流式通信(如实时推送、聊天) - 多语言混合的微服务架构(Java、Go、Python 互调) - 云原生环境(Kubernetes 生态天然支持)
❌ 不适合 gRPC 的场景: - 对外暴露的公共 API(浏览器兼容性差) - 需要人工调试接口(二进制不可读) - 团队对 Protobuf 不熟悉,学习成本高
九、面试常见问题¶
Q:gRPC 为什么比 REST 性能高?
两个原因:① Protobuf 二进制序列化体积更小、解析更快;② HTTP/2 多路复用减少了连接建立开销。
Q:Protobuf 字段编号为什么不能随意修改?
Protobuf 序列化时用字段编号而非字段名标识字段。修改编号会导致新旧版本数据不兼容,反序列化出错。正确做法是只新增字段,废弃字段用
reserved标记。
Q:gRPC 如何做负载均衡?
gRPC 支持客户端负载均衡(通过
NameResolver+LoadBalancer)和服务端负载均衡(通过 Nginx/Envoy 代理)。在 Kubernetes 中通常配合 Istio 或 Envoy 做服务网格级别的负载均衡。
Q:gRPC 和 Dubbo 怎么选?
- Java 技术栈为主、国内团队 → 优先考虑 Dubbo(生态成熟、文档丰富)
- 多语言混合、云原生/K8s 环境 → 优先考虑 gRPC(跨语言强、CNCF 标准)