4. gRPC 基础入门:核心概念与通信模式

在微服务架构中,服务间通信的效率和可靠性至关重要。gRPC 作为高性能、开源的远程过程调用(RPC)框架,已成为云原生生态中的通信标准。本章将系统介绍 gRPC 的核心概念、序列化基础以及四种通信模式,为后续的健康检查与可用性保障方案打下坚实基础。

4.1 gRPC 简介

gRPC 由 Google 开发,基于 HTTP/2 协议传输,使用 Protocol Buffers 作为接口定义语言(IDL)和序列化工具。其核心优势包括:

  • 高性能:基于 HTTP/2 的多路复用、头部压缩和二进制帧传输,显著降低延迟和带宽消耗。
  • 跨语言:支持 C++、Java、Go、Python、Node.js 等十余种语言,服务定义与实现解耦。
  • 强类型:通过 .proto 文件定义服务和消息结构,编译生成客户端和服务端桩代码,避免运行时类型错误。
  • 流式通信:原生支持四种通信模式,适应实时推送、大数据传输等场景。
关键概念: gRPC 的通信单元是 Service(服务)和 RPC 方法。每个 RPC 方法接收一个或多个请求消息,返回一个或多个响应消息,具体模式由方法签名决定。

4.2 Protocol Buffers 基础

Protocol Buffers(简称 Protobuf)是 gRPC 的默认序列化协议。它通过 .proto 文件定义数据结构,并使用编译器(protoc)生成各语言的代码。

4.2.1 消息定义

以下是一个典型的 .proto 文件示例,定义了一个简单的用户服务和健康检查消息:

syntax = "proto3";

package healthcheck;

// 健康检查请求
message HealthCheckRequest {
  string service_name = 1;
}

// 健康检查响应
message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
    SERVICE_UNKNOWN = 3;
  }
  ServingStatus status = 1;
}

// 健康检查服务
service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

4.2.2 字段类型与编号

类型 说明 示例
标量类型 int32、int64、float、double、bool、string、bytes int32 id = 1;
枚举 预定义常量集合 enum Status { OK = 0; ERROR = 1; }
嵌套消息 消息内部定义其他消息 message Result { string msg = 1; }
repeated 可变长度数组(列表) repeated string tags = 3;
map 键值对映射 map<string, int32> scores = 4;
最佳实践: 字段编号 1-15 使用 1 字节编码,应留给频繁出现的字段;16-2047 使用 2 字节编码。预留编号范围 19000-19999 供 Protobuf 内部使用。

4.3 gRPC 四种通信模式

gRPC 基于 HTTP/2 的流式特性,定义了四种 RPC 通信模式。理解这些模式是设计微服务接口的关键。

4.3.1 模式对比总览

模式 请求 响应 典型场景
Unary(一元) 单个请求 单个响应 查询、写入、认证
Server Streaming(服务端流) 单个请求 流式响应 日志推送、监控数据流
Client Streaming(客户端流) 流式请求 单个响应 批量上传、聚合计算
Bidirectional Streaming(双向流) 流式请求 流式响应 实时聊天、协同编辑

4.3.2 四种模式详解

1. Unary RPC(一元 RPC)

客户端发送一个请求,服务端返回一个响应。这是最基础的 RPC 模式,行为类似传统 HTTP 请求-响应模型。

// .proto 定义
rpc GetUser(GetUserRequest) returns (User);

// 客户端调用(Go 示例)
user, err := client.GetUser(ctx, &pb.GetUserRequest{UserId: 123})

2. Server Streaming RPC(服务端流 RPC)

客户端发送一个请求,服务端返回一个消息流。客户端持续读取流直到结束。适用于服务端需要推送大量数据的场景。

// .proto 定义
rpc SubscribeEvents(SubscribeRequest) returns (stream Event);

// 客户端读取流
stream, _ := client.SubscribeEvents(ctx, &pb.SubscribeRequest{Topic: "orders"})
for {
    event, err := stream.Recv()
    if err == io.EOF { break }
    // 处理 event
}

3. Client Streaming RPC(客户端流 RPC)

客户端发送一个消息流,服务端在接收完所有消息后返回一个响应。适用于客户端需要上传大量数据的场景。

// .proto 定义
rpc UploadLogs(stream LogEntry) returns (UploadSummary);

// 客户端发送流
stream, _ := client.UploadLogs(ctx)
for _, log := range logs {
    stream.Send(&pb.LogEntry{Message: log})
}
summary, _ := stream.CloseAndRecv()

4. Bidirectional Streaming RPC(双向流 RPC)

客户端和服务端可以独立地发送和接收消息流,两者是异步并行的。适用于实时交互场景。

// .proto 定义
rpc Chat(stream ChatMessage) returns (stream ChatMessage);

// 客户端同时发送和接收
stream, _ := client.Chat(ctx)
go func() {
    for _, msg := range messages {
        stream.Send(&pb.ChatMessage{Content: msg})
    }
    stream.CloseSend()
}()
for {
    reply, err := stream.Recv()
    if err == io.EOF { break }
    // 处理回复
}
注意: 在双向流中,发送和接收操作是独立的。客户端可以在未收到所有响应前继续发送请求,服务端也可以在未收到所有请求前开始发送响应。这种异步特性需要仔细处理并发和背压(backpressure)。

4.4 gRPC 通信模式架构图

下图展示了四种通信模式在客户端与服务端之间的数据流关系:

gRPC 四种通信模式数据流 客户端 服务端 ① Unary RPC 请求 响应 ② Server Streaming 请求 流式响应 ③ Client Streaming 流式请求 响应 ④ Bidirectional Streaming 双向独立流

4.5 模式选择决策流程

在实际微服务设计中,应根据业务需求选择合适的通信模式。以下决策流程可帮助快速判断:

gRPC 通信模式选择决策流程 选择通信模式 客户端发送单个请求? 是 否 服务端返回单个响应? 服务端返回单个响应? 是 Unary RPC 否 Server Streaming 是 Client Streaming 否 Bidirectional Streaming 提示:双向流适用于客户端和服务端都需要独立发送多个消息的场景, 如实时聊天、协同编辑、实时数据同步等。

4.6 本章小结

  • gRPC 基于 HTTP/2 和 Protocol Buffers,提供高性能、跨语言的 RPC 通信能力。
  • Protocol Buffers 通过 .proto 文件定义消息和服务,支持标量、枚举、嵌套、repeated 和 map 等类型。
  • 四种通信模式(Unary、Server Streaming、Client Streaming、Bidirectional Streaming)覆盖了从简单请求-响应到复杂实时交互的全部场景。
  • 选择通信模式时,应首先判断请求和响应的数量关系,再结合业务场景确定最合适的模式。