3. gRPC 服务定义:Service 定义、RPC 方法类型与生成代码流程

gRPC 的核心是使用 Protocol Buffers(protobuf) 作为接口定义语言(IDL)。开发者首先在 .proto 文件中定义 Service(服务)和 RPC 方法,然后通过 protoc 编译器生成对应语言的客户端和服务端代码。本章将详细拆解 Service 定义语法、四种 RPC 方法类型以及完整的代码生成流程。

3.1 Service 定义语法

一个 gRPC Service 在 .proto 文件中通过 service 关键字声明,内部包含一个或多个 rpc 方法。每个 RPC 方法必须指定请求消息类型和响应消息类型。

核心语法结构:
syntax = "proto3";

package helloworld;

// 定义请求消息
message HelloRequest {
  string name = 1;
}

// 定义响应消息
message HelloReply {
  string message = 1;
}

// 定义 gRPC 服务
service Greeter {
  // 一元 RPC
  rpc SayHello (HelloRequest) returns (HelloReply);
}
元素 说明 示例
syntax 指定 protobuf 版本,目前主流为 proto3 syntax = "proto3";
package 防止命名冲突,生成代码时对应包名/命名空间 package helloworld;
message 定义结构化数据,字段有类型、名称和唯一编号 message HelloRequest { string name = 1; }
service 定义 gRPC 服务接口,包含一组 RPC 方法 service Greeter { ... }
rpc 声明一个远程调用方法,指定请求和响应类型 rpc SayHello (HelloRequest) returns (HelloReply);
字段编号规则: proto3 中每个 message 字段必须有一个唯一的数字编号(1 到 536,870,911)。编号 1-15 使用 1 字节编码,应留给频繁使用的字段;编号 16-2047 使用 2 字节编码。19000-19999 为保留编号,不可使用。

3.2 四种 RPC 方法类型

gRPC 支持四种通信模式,分别适用于不同的业务场景。理解这些模式是设计高性能服务的基础。

四种 RPC 方法类型对比 Unary(一元) 客户端发送 1 个请求 服务端返回 1 个响应 类似传统 RPC / HTTP 最常用,简单直接 适合请求-响应模式 rpc Foo(Request) returns (Response); Server Streaming 客户端发送 1 个请求 服务端返回多个响应 流式推送数据 适合大文件下载 实时事件推送 rpc Foo(Request) returns (stream Response); Client Streaming 客户端发送多个请求 服务端返回 1 个响应 批量上传数据 客户端聚合发送 适合日志收集 rpc Foo(stream Request) returns (Response); Bidirectional 双方独立读写流 全双工通信 实时聊天 游戏状态同步 实时协同编辑 rpc Foo(stream Request) returns (stream Response); 关键区别: Unary 是标准请求-响应;Server Streaming 是“一请求多响应”;Client Streaming 是“多请求一响应”;Bidirectional 是“多请求多响应”的全双工模式。
流式传输注意事项: 流式 RPC 虽然提供了强大的实时能力,但会增加连接管理和背压(backpressure)处理的复杂度。在压测场景中,流式 RPC 的尾延迟分布通常比 Unary 更分散,需要特别关注 P99.9 指标。

3.3 生成代码流程

从 .proto 文件生成可用的 gRPC 代码需要经过以下步骤。以 Go 语言为例,展示完整的工具链和命令。

gRPC 代码生成流程 步骤 1 编写 .proto 文件 定义 Service & Message 步骤 2 安装 protoc 编译器 及语言插件 步骤 3 执行 protoc 命令 指定 proto 路径 步骤 4 生成 .pb.go 文件 及 _grpc.pb.go 文件 生成产物说明(以 Go 语言为例) • helloworld.pb.go:包含所有 Message 的序列化/反序列化代码、字段访问方法。 • helloworld_grpc.pb.go:包含 Service 的客户端 Stub 接口、服务端注册接口、 以及对应的 gRPC 调用实现。 • 其他语言(Java/Python/C++)会生成对应语言的类/模块,结构类似。

3.4 完整生成命令示例(Go)

以下是在 Go 项目中生成 gRPC 代码的完整命令和目录结构。

安装工具:
# 安装 protoc 编译器(macOS)
brew install protobuf

# 安装 Go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 确保 $GOPATH/bin 在 PATH 中
export PATH="$PATH:$(go env GOPATH)/bin"
执行代码生成:
protoc --go_out=. \
       --go-grpc_out=. \
       --proto_path=. \
       helloworld/helloworld.proto
参数 说明
--go_out 指定 protobuf 消息代码的输出目录
--go-grpc_out 指定 gRPC 服务代码的输出目录
--proto_path 指定 .proto 文件的搜索路径,可多次使用
helloworld/helloworld.proto 要编译的 proto 文件路径
生成后的目录结构:
project/
├── helloworld/
│   ├── helloworld.proto          # 原始定义
│   ├── helloworld.pb.go          # 消息代码
│   └── helloworld_grpc.pb.go     # gRPC 服务代码
├── server/
│   └── main.go                   # 服务端实现
└── client/
    └── main.go                   # 客户端实现

3.5 四种 RPC 方法的 proto 定义示例

以下是一个完整的 .proto 文件,展示了四种 RPC 方法的定义方式。

完整示例:
syntax = "proto3";

package streamdemo;

message Request {
  int32 id = 1;
  string payload = 2;
}

message Response {
  int32 id = 1;
  string result = 2;
}

service StreamService {
  // Unary:标准请求-响应
  rpc UnaryCall (Request) returns (Response);

  // Server Streaming:服务端流式推送
  rpc ServerStreaming (Request) returns (stream Response);

  // Client Streaming:客户端流式上传
  rpc ClientStreaming (stream Request) returns (Response);

  // Bidirectional Streaming:双向流
  rpc BidirectionalStreaming (stream Request) returns (stream Response);
}
流式 RPC 的压测要点: 在尾延迟压测中,流式 RPC 的延迟分布通常呈现“长尾更重”的特征。建议在压测脚本中分别统计每个流消息的到达间隔(inter-arrival time)和端到端延迟,而不是仅统计整个流的完成时间。这能更准确地反映服务端处理流消息的真实性能。