3. gRPC 服务定义:service 关键字与 RPC 方法类型

在 gRPC 的接口契约体系中,service 关键字是定义远程过程调用的核心入口。它通过 Protocol Buffers(Protobuf)的 IDL(接口定义语言)将一组相关的 RPC 方法聚合为一个逻辑服务单元。每个 RPC 方法必须明确指定其通信模式,这直接决定了客户端与服务端之间的数据流形态。

3.1 service 关键字与基本语法

在 .proto 文件中,使用 service 关键字声明一个 gRPC 服务,内部包含一个或多个 rpc 方法定义。每个方法需指定请求消息类型和响应消息类型。

核心语法结构:
service ServiceName {
  rpc MethodName(RequestMessage) returns (ResponseMessage);
}

其中 RequestMessage 和 ResponseMessage 必须是已在同一 .proto 文件或导入文件中定义的 Protobuf 消息类型。

以下是一个报表导出场景中的服务定义示例:

syntax = "proto3";

package reportexport;

// 导出任务相关的消息定义
message ExportRequest {
  string report_id = 1;
  string format = 2;       // pdf, csv, xlsx
  int32 page_size = 3;
}

message ExportResponse {
  string task_id = 1;
  ExportStatus status = 2;
}

message ExportStatus {
  enum State {
    PENDING = 0;
    PROCESSING = 1;
    COMPLETED = 2;
    FAILED = 3;
  }
  State state = 1;
  float progress = 2;       // 0.0 ~ 1.0
  string download_url = 3;
}

// 定义导出服务
service ReportExportService {
  // 同步导出(Unary)
  rpc SyncExport(ExportRequest) returns (ExportResponse);

  // 订阅导出进度(Server Streaming)
  rpc SubscribeExportProgress(ExportRequest) returns (stream ExportStatus);

  // 批量提交导出任务(Client Streaming)
  rpc BatchSubmitExport(stream ExportRequest) returns (ExportResponse);

  // 实时双向导出控制(Bidirectional Streaming)
  rpc LiveExportControl(stream ExportRequest) returns (stream ExportStatus);
}

3.2 四种 RPC 方法类型详解

gRPC 基于 HTTP/2 的流式传输能力,定义了四种 RPC 方法类型。理解它们的差异是设计高效接口契约的前提。

方法类型 请求流 响应流 典型场景 数据交互特点
Unary 单条消息 单条消息 简单查询、同步导出、用户认证 类 HTTP 请求/响应,最常用
Server Streaming 单条消息 多条消息(流) 导出进度推送、日志订阅、实时监控 服务端主动推送,客户端被动接收
Client Streaming 多条消息(流) 单条消息 批量数据上传、大文件分片提交 客户端批量发送,服务端汇总响应
Bidirectional Streaming 多条消息(流) 多条消息(流) 实时控制台、双向数据同步、交互式导出 全双工通信,两端独立读写

3.3 四种方法类型的流式交互模型

下图展示了四种 RPC 方法在客户端与服务端之间的消息流方向与生命周期差异。箭头方向表示数据流动方向,矩形代表消息单元。

gRPC 四种 RPC 方法类型交互模型 四种 RPC 方法类型交互模型 客户端 服务端 ① Unary 请求 响应 ② Server Streaming 请求 流响应 ③ Client Streaming 流请求 响应 ④ Bidirectional Streaming 流请求 流响应

3.4 流式方法在导出场景中的选择策略

在报表导出架构中,不同导出阶段对通信模式有不同要求。以下策略可帮助你在接口契约设计时做出正确选择:

  • 同步导出(Unary):适用于小报表(数据量 < 10MB),客户端发起请求后阻塞等待完整响应。优点是实现简单,缺点是长时间占用连接。
  • 进度订阅(Server Streaming):适用于大报表导出,客户端发起一次请求后,服务端持续推送 ExportStatus 消息,包含进度百分比和当前阶段描述。客户端可实时更新 UI 进度条。
  • 批量提交(Client Streaming):适用于客户端需要分片上传大量导出参数或数据源的场景。例如,用户一次性勾选 1000 个报表 ID,客户端分批发送,服务端汇总后返回一个总任务 ID。
  • 实时控制(Bidirectional Streaming):适用于交互式导出控制台。客户端可以随时发送暂停、取消、修改参数等指令,服务端同时返回当前状态和结果流。这是最灵活但也是最复杂的模式。
设计建议: 在异步导出链路中,Server Streaming 是最常用的模式。它既能避免 Unary 的长连接阻塞问题,又比 Bidirectional Streaming 更容易实现幂等性和重试逻辑。建议优先考虑将导出进度接口设计为 Server Streaming 模式。

3.5 流式方法的错误处理与生命周期

流式 RPC 的错误处理与 Unary 有显著差异。服务端可以在流中的任意时刻发送错误,客户端需要正确处理流的中断和重连。

场景 Unary Server Streaming Client Streaming Bidirectional Streaming
错误时机 响应返回时 流中任意位置 客户端发送完毕后 流中任意位置
客户端处理 检查状态码 监听 onError 回调 等待服务端响应 监听 onError 回调
重连策略 直接重试 重新发起订阅 重新发送所有数据 重新建立双向流
幂等性要求 低 中(需去重) 高(需去重) 高(需状态同步)

下图展示了在导出进度订阅场景中,Server Streaming 模式下服务端如何通过流式消息逐步推送状态,以及客户端如何处理流中断与重连:

Server Streaming 导出进度订阅流程 Server Streaming 导出进度订阅流程 客户端 服务端 ExportRequest Status 10% Status 45% Status 80% 流中断错误 重连请求 客户端需记录已接收进度 重连时从断点继续订阅
常见陷阱: 在 Server Streaming 中,如果服务端在流中间返回错误,客户端收到的部分消息可能已经处理。此时重连时必须携带上次成功处理的消息偏移量或进度标记,否则会导致重复处理或进度回退。建议在 ExportStatus 消息中增加 sequence_number 字段用于去重。

3.6 方法类型选择决策树

以下决策树可帮助你在设计新接口时快速确定应使用哪种 RPC 方法类型:

  1. 客户端是否需要发送多条消息?
    • 否 → 进入第 2 步
    • 是 → 进入第 3 步
  2. 服务端是否需要返回多条消息?
    • 否 → Unary
    • 是 → Server Streaming
  3. 服务端是否需要返回多条消息?
    • 否 → Client Streaming
    • 是 → Bidirectional Streaming

这个决策树覆盖了 90% 以上的接口设计场景。在报表导出架构中,Unary 用于简单查询和同步导出,Server Streaming 用于进度推送,Client Streaming 用于批量提交,Bidirectional Streaming 用于交互式控制台。