gRPC 与 Protobuf 深入解析——从 IDL 定义到 HTTP/2 传输
一、引言
gRPC 是 Google 开源的、基于 HTTP/2 的高性能 RPC 框架,默认使用 Protocol Buffers(Protobuf)作为接口定义语言(IDL)和序列化协议。在微服务架构中,gRPC 正逐渐成为服务间通信的首选方案,尤其适合高吞吐、低延迟的场景。
相比于传统的 RESTful API,gRPC 提供了强类型接口定义、高效的二进制序列化、多语言支持以及四种通信模式。本文将从 Protobuf 编码原理出发,深入分析 gRPC 的四种通信模式,解析 HTTP/2 协议层面的工作方式,最后以 Spring Boot 集成为例展示生产级实践。
二、Protobuf 编码原理
2.1 为什么选择 Protobuf
| 特性 | Protobuf | JSON | XML |
|---|---|---|---|
| 序列化大小 | 小(约 JSON 的 1/3) | 中等 | 大 |
| 序列化速度 | 极快 | 较快 | 慢 |
| 可读性 | 需 .proto 文件 | 好 | 好 |
| Schema 定义 | 强类型 | 无 Schema | XSD |
| 向后兼容 | 支持字段级别演进 | – | – |
| 多语言支持 | 官方支持 10+ 语言 | 所有语言 | 所有语言 |
2.2 定义 .proto 文件
syntax = "proto3";
package ecommerce;
option java_package = "com.example.proto";
option java_multiple_files = true;
import "google/protobuf/timestamp.proto";
// 服务定义
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc ListOrders(ListOrdersRequest) returns (stream Order);
rpc CreateOrder(stream CreateOrderRequest) returns (CreateOrderResponse);
rpc ProcessOrder(stream OrderEvent) returns (stream OrderStatus);
}
// 枚举
enum OrderStatus {
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_CREATED = 1;
ORDER_STATUS_PAID = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_DELIVERED = 4;
ORDER_STATUS_CANCELLED = 5;
}
// 订单消息
message Order {
int64 id = 1;
string order_no = 2;
int64 user_id = 3;
repeated OrderItem items = 4;
Address shipping_address = 5;
OrderStatus status = 6;
double total_amount = 7;
google.protobuf.Timestamp created_at = 8;
}
message OrderItem {
string sku = 1;
string name = 2;
int32 quantity = 3;
double price = 4;
}
message Address {
string province = 1;
string city = 2;
string district = 3;
string detail = 4;
}
message GetOrderRequest {
int64 id = 1;
}
message ListOrdersRequest {
int64 user_id = 1;
OrderStatus status = 2;
int32 page_size = 3;
string page_token = 4;
}
2.3 Varint 编码
Protobuf 使用 Varint(可变长整数)编码来压缩整数类型。小整数占用更少的字节:
| 值 | 二进制 | Varint 编码 |
|---|---|---|
| 1 | 0000 0001 | 0000 0001 (1 byte) |
| 127 | 0111 1111 | 0111 1111 (1 byte) |
| 128 | 1000 0000 | 1000 0000 0000 0001 (2 bytes) |
| 300 | 0001 0010 1100 | 1010 1100 0000 0010 (2 bytes) |
// Varint 编码的简化实现
public class VarintExample {
public static byte[] encodeVarint(long value) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (true) {
if ((value & ~0x7FL) == 0) {
bos.write((byte) value);
return bos.toByteArray();
} else {
bos.write((byte) ((value & 0x7F) | 0x80));
value >>>= 7;
}
}
}
public static long decodeVarint(byte[] data) {
long result = 0;
int shift = 0;
for (byte b : data) {
result |= (long) (b & 0x7F) << shift;
if ((b & 0x80) == 0) return result;
shift += 7;
}
throw new IllegalArgumentException("Malformed varint");
}
}
2.4 Wire Type 格式
每个 Protobuf 字段的编码由 (field_number << 3) | wire_type 构成:
| Wire Type | 含义 | 编码方式 | 字段类型 |
|---|---|---|---|
| 0 | Varint | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
| 1 | 64-bit | 固定 8 字节 | fixed64, sfixed64, double |
| 2 | Length-delimited | 长度 + 值 | string, bytes, embedded messages, packed repeated fields |
| 3 | Start group | 已废弃 | group(proto2 only) |
| 4 | End group | 已废弃 | group(proto2 only) |
| 5 | 32-bit | 固定 4 字节 | fixed32, sfixed32, float |
// Order.id = 1 的编码
field_number = 1, wire_type = 0 (Varint)
tag = (1 << 3) | 0 = 0x08
value = Varint(1) = 0x01
编码:08 01
// Order.order_no = "ORD001" 的编码
field_number = 2, wire_type = 2 (Length-delimited)
tag = (2 << 3) | 2 = 0x12
length = 6
value = "ORD001"
编码:12 06 4F 52 44 30 30 31
三、gRPC 四种通信模式
3.1 Unary RPC(一元模式)
最简单的客户端-服务端一对一模式:
rpc GetOrder(GetOrderRequest) returns (Order);
// Server 端
public class OrderServiceImpl extends OrderServiceGrpc.OrderServiceImplBase {
@Override
public void getOrder(GetOrderRequest request, StreamObserver<Order> responseObserver) {
Order order = orderRepository.findById(request.getId())
.orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND));
responseObserver.onNext(order);
responseObserver.onCompleted();
}
}
// Client 端
OrderServiceGrpc.OrderServiceBlockingStub stub = OrderServiceGrpc.newBlockingStub(channel);
GetOrderRequest request = GetOrderRequest.newBuilder().setId(1L).build();
Order order = stub.getOrder(request);
System.out.println("Order: " + order.getOrderNo());
3.2 Server Streaming RPC(服务端流式)
服务端返回一系列消息流:
rpc ListOrders(ListOrdersRequest) returns (stream Order);
sequenceDiagram
participant Client as gRPC Client
participant Server as gRPC Server
Client->>Server: ListOrders(page_size=50)
Server->>Server: Query first batch
Server-->>Client: Order(1), Order(2), ..., Order(50)
Server->>Server: Query second batch
Server-->>Client: Order(51), Order(52), ..., Order(100)
Server-->>Client: Stream completed
// Server 端
@Override
public void listOrders(ListOrdersRequest request, StreamObserver<Order> responseObserver) {
int pageSize = request.getPageSize() > 0 ? request.getPageSize() : 50;
int offset = 0;
List<Order> batch;
do {
batch = orderRepository.findBatch(offset, pageSize);
for (Order order : batch) {
responseObserver.onNext(order);
}
offset += pageSize;
} while (batch.size() == pageSize);
responseObserver.onCompleted();
}
// Client 端——异步遍历
Iterator<Order> orders = stub.listOrders(ListOrdersRequest.newBuilder()
.setUserId(1001L)
.setPageSize(50)
.build());
while (orders.hasNext()) {
Order order = orders.next();
System.out.println("Received: " + order.getOrderNo());
}
3.3 Client Streaming RPC(客户端流式)
客户端发送多条消息,服务端返回单条响应:
rpc CreateOrder(stream CreateOrderRequest) returns (CreateOrderResponse);
// Server 端
@Override
public StreamObserver<CreateOrderRequest> createOrder(
StreamObserver<CreateOrderResponse> responseObserver) {
return new StreamObserver<>() {
private final List<Order> pendingOrders = new ArrayList<>();
@Override
public void onNext(CreateOrderRequest request) {
Order order = buildOrder(request);
pendingOrders.add(order);
}
@Override
public void onError(Throwable t) {
log.error("Error creating orders", t);
}
@Override
public void onCompleted() {
// 批量创建
List<Long> orderIds = orderRepository.batchSave(pendingOrders);
CreateOrderResponse response = CreateOrderResponse.newBuilder()
.addAllOrderIds(orderIds)
.setSuccessCount(orderIds.size())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
};
}
// Client 端——批量上传
StreamObserver<CreateOrderRequest> requestObserver = stub.createOrder(asyncObserver);
for (OrderItem item : items) {
CreateOrderRequest request = CreateOrderRequest.newBuilder()
.setUserId(1001L)
.setSku(item.getSku())
.setQuantity(item.getQuantity())
.build();
requestObserver.onNext(request);
}
requestObserver.onCompleted();
3.4 Bidirectional Streaming RPC(双向流式)
客户端和服务端同时发送和接收消息流:
rpc ProcessOrder(stream OrderEvent) returns (stream OrderStatus);
sequenceDiagram
participant Client as gRPC Client
participant Server as gRPC Server
Client->>Server: OrderEvent(order_id=1, event="payment_confirmed")
Client->>Server: OrderEvent(order_id=2, event="payment_confirmed")
Server-->>Client: OrderStatus(order_id=1, status="PAID")
Client->>Server: OrderEvent(order_id=1, event="shipped")
Server-->>Client: OrderStatus(order_id=2, status="PAID")
Server-->>Client: OrderStatus(order_id=1, status="SHIPPED")
Client->>Server: Stream completed
Server-->>Client: Stream completed
四、gRPC 与 HTTP/2 的关系
4.1 HTTP/2 核心特性
| 特性 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 连接复用 | 有限(Connection: keep-alive) | 多路复用(Stream) |
| 头部压缩 | 无,每次传输完整 Header | HPACK 压缩 |
| 二进制传输 | 文本 | 二进制帧 |
| 服务器推送 | 无 | Server Push |
| 流优先级 | 无 | 支持 |
4.2 gRPC over HTTP/2
gRPC 利用 HTTP/2 的以下特性:
- 多路复用:一个 TCP 连接上同时处理多个 RPC 调用
- 流控制:基于连接的流量控制,防止慢消费者被淹没
- 头部压缩:gRPC 的 Headers 被 HPACK 高效压缩
- 二进制帧:Protobuf 的二进制负载天然与 HTTP/2 二进制帧匹配
gRPC over HTTP/2 的帧格式:
┌─────────────────────────────────────────────┐
│ HTTP/2 HEADERS frame (gRPC CALL) │
│ :method = POST │
│ :scheme = https │
│ :path = /ecommerce.OrderService/GetOrder │
│ content-type = application/grpc+proto │
│ grpc-timeout = 5S │
│ te = trailers │
├─────────────────────────────────────────────┤
│ HTTP/2 DATA frame (gRPC Message) │
│ 5 bytes header: │
│ 1 byte: 压缩标志 (0=uncompressed) │
│ 4 bytes: 消息长度 │
│ Protobuf 负载 │
├─────────────────────────────────────────────┤
│ HTTP/2 HEADERS frame (Trailers) │
│ grpc-status = 0 │
│ grpc-message = OK │
└─────────────────────────────────────────────┘
五、Spring Boot 集成 gRPC
5.1 项目依赖
net.devh
grpc-server-spring-boot-starter
3.0.0.RELEASE
net.devh
grpc-client-spring-boot-starter
3.0.0.RELEASE
com.github.os72
protoc-jar-maven-plugin
3.11.4
generate-sources
run
3.21.12
src/main/proto
5.2 服务端实现
# application.yml
grpc:
server:
port: 9090
max-inbound-message-size: 16MB
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
@Autowired
private OrderRepository orderRepository;
@Override
public void getOrder(GetOrderRequest request,
StreamObserver<Order> responseObserver) {
try {
OrderEntity entity = orderRepository.findById(request.getId())
.orElseThrow(() -> new RuntimeException("Order not found"));
Order proto = toProto(entity);
responseObserver.onNext(proto);
responseObserver.onCompleted();
} catch (Exception e) {
responseObserver.onError(
Status.NOT_FOUND.withDescription(e.getMessage()).asRuntimeException());
}
}
@Override
public void listOrders(ListOrdersRequest request,
StreamObserver<Order> responseObserver) {
List<OrderEntity> entities = orderRepository.findByUserId(
request.getUserId(),
PageRequest.of(0, request.getPageSize() > 0 ? request.getPageSize() : 50));
for (OrderEntity entity : entities) {
responseObserver.onNext(toProto(entity));
}
responseObserver.onCompleted();
}
private Order toProto(OrderEntity entity) {
return Order.newBuilder()
.setId(entity.getId())
.setOrderNo(entity.getOrderNo())
.setUserId(entity.getUserId())
.setTotalAmount(entity.getTotalAmount().doubleValue())
.setStatus(OrderStatus.valueOf(entity.getStatus()))
.build();
}
}
5.3 客户端调用
@GrpcClient("order-service")
private Channel channel;
public OrderDTO getOrder(Long orderId) {
OrderServiceGrpc.OrderServiceBlockingStub stub =
OrderServiceGrpc.newBlockingStub(channel)
.withDeadlineAfter(5, TimeUnit.SECONDS);
GetOrderRequest request = GetOrderRequest.newBuilder()
.setId(orderId)
.build();
Order order = stub.getOrder(request);
return toDTO(order);
}
// gRPC + 负载均衡
@Configuration
public class GrpcClientConfig {
@Bean
@GrpcClient("order-service")
public Channel orderServiceChannel() {
return ManagedChannelBuilder.forTarget("consul://order-service")
.nameResolverFactory(new ConsulNameResolverProvider())
.defaultLoadBalancingPolicy("round_robin")
.usePlaintext()
.build();
}
}
5.4 拦截器
// 服务端拦截器 —— 日志和监控
@GrpcGlobalServerInterceptor
public class LogGrpcInterceptor implements ServerInterceptor {
private static final MeterRegistry registry = new SimpleMeterRegistry();
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String methodName = call.getMethodDescriptor().getFullMethodName();
Timer.Sample sample = Timer.start(registry);
ServerCall<ReqT, RespT> wrappedCall = new ForwardingServerCall.SimpleForwardingServerCall<>(call) {
@Override
public void close(Status status, Metadata trailers) {
sample.stop(Timer.builder("grpc.server.duration")
.tag("method", methodName)
.tag("status", status.getCode().name())
.register(registry));
super.close(status, trailers);
}
};
log.info("gRPC call: {}", methodName);
return next.startCall(wrappedCall, headers);
}
}
六、性能对比与最佳实践
6.1 gRPC vs REST 性能对比
| 维度 | gRPC | REST (JSON) |
|---|---|---|
| 序列化大小 | 约 100 bytes | 约 300 bytes |
| 序列化耗时 | 0.5 μs | 2 μs |
| 反序列化耗时 | 0.3 μs | 1.5 μs |
| 传输协议 | HTTP/2 | HTTP/1.1 |
| 延迟 | 低(连接复用) | 高(多次连接) |
| 吞吐量 | 高(多路复用) | 低 |
6.2 最佳实践
- Deadline 设置:每个 RPC 调用必须设置超时
- 重试策略:幂等操作配置重试,非幂等操作谨慎重试
- 流控管理:服务端合理配置
maxInboundMessageSize - 错误处理:使用标准的 gRPC 错误码和详细的错误描述
- 健康检查:实现
grpc.health.v1.Health服务 - TLS 加密:生产环境必须使用 mTLS
七、总结
gRPC 和 Protobuf 的组合构建了性能优异的微服务通信基础设施。通过本文的分析可以看出:
- Protobuf 编码:Varint 编码和 Wire Type 格式使其序列化效率远超 JSON
- 四种通信模式:Unary、Server Streaming、Client Streaming、Bidirectional Streaming 覆盖了从简单请求响应到复杂流处理的全部场景
- HTTP/2 基础:多路复用、头部压缩和二进制帧为 gRPC 的高性能奠定了基础
- 生态集成:通过 Spring Boot Starter 可以便捷地将 gRPC 集成到现有微服务体系中
gRPC 最适合内部服务间的高频通信场景。在外部 API 暴露场景中,结合 gRPC Gateway 或 Envoy 可以同时提供 gRPC 和 RESTful 接口,既保证了内部通信的高效性,也兼顾了外部接入的便利性。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容