跳到主要内容

客户端架构

本章从概念层面介绍 gRPC 客户端的核心架构和设计原则,帮助理解客户端的工作机制。具体语言的实现细节请参考对应的语言开发章节。

客户端核心概念

架构概览

gRPC 客户端的架构可以抽象为以下几个层次:

Stub(存根)概念

Stub 是客户端与服务端交互的核心抽象。它封装了网络通信细节,让客户端像调用本地方法一样调用远程服务:

Stub 的职责

  • 将方法调用转换为 RPC 请求
  • 序列化请求对象
  • 通过 Channel 发送请求
  • 接收并反序列化响应
  • 处理错误和重试

Channel(通道)管理

Channel 的本质

Channel 是客户端与服务端之间的虚拟连接抽象。它代表了一组可以复用的 HTTP/2 连接:

Channel 的生命周期

状态说明
IDLE空闲,无活动连接
CONNECTING正在建立连接
READY连接就绪,可发送请求
TRANSIENT_FAILURE临时故障,正在重试
SHUTDOWN已关闭

连接复用的重要性

Channel 是重量级资源,创建开销大:

创建连接的成本

  • DNS 解析
  • TCP 三次握手
  • TLS 握手(如启用)
  • HTTP/2 协商

复用连接的好处

  • 避免重复的连接建立开销
  • 减少服务端负载
  • 提高请求响应速度
  • 更好地利用 HTTP/2 多路复用

最佳实践

  • 应用生命周期内复用 Channel
  • 使用单例模式管理 Channel
  • 避免为每个请求创建新 Channel

负载均衡

负载均衡架构

gRPC 支持两种负载均衡架构:

代理 vs 客户端负载均衡

特性代理负载均衡客户端负载均衡
部署复杂度需要额外代理组件无需额外组件
网络跳数多一跳直连后端
后端感知代理感知客户端感知
适用场景公网、不可信客户端内部微服务
状态维护代理维护客户端维护
故障转移代理处理客户端处理

负载均衡策略

gRPC 内置两种负载均衡策略:

pick_first(默认)

  • 选择第一个可用地址
  • 不进行负载均衡
  • 适用于单服务端或已有外部 LB 的场景

round_robin

  • 轮询所有可用后端
  • 基于连接的负载均衡
  • 适用于内部微服务

服务发现

gRPC 使用名称解析器(Name Resolver)获取服务地址:

解析器类型格式说明
DNSdns:///host:portDNS 解析
静态地址host:port,host:port直接指定
自定义scheme:///service自定义解析器

常见服务发现集成

  • Consul
  • etcd
  • Zookeeper
  • Nacos
  • Kubernetes DNS

超时与Deadline

Deadline 机制

gRPC 使用 Deadline(截止时间)而非简单的超时来控制请求时长:

Deadline vs Timeout

特性DeadlineTimeout
定义方式绝对时间点相对时长
传播性自动传播到下游需要手动计算
精确性全局一致每跳累积误差
推荐程度推荐不推荐

Deadline 传播

当服务 A 调用服务 B 时,Deadline 会自动传播:

  • 服务 B 收到的是剩余时间,而非原始超时
  • 确保整个调用链在统一的时间点超时
  • 避免级联超时导致的资源浪费

超时设置原则

  1. 设置合理的超时:根据业务场景设置
  2. 区分操作类型:读写操作使用不同超时
  3. 考虑网络延迟:公网比内网需要更长超时
  4. 监控实际延迟:根据 P99 延迟调整

重试机制

重试策略配置

gRPC 支持声明式的重试配置:

{
"methodConfig": [{
"name": [{"service": ""}],
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}

重试配置参数

参数说明推荐值
maxAttempts最大尝试次数(含首次)3-5
initialBackoff初始退避时间0.1s-1s
maxBackoff最大退避时间1s-10s
backoffMultiplier退避乘数2
retryableStatusCodes可重试的错误码UNAVAILABLE

可重试的错误码

并非所有错误都适合重试:

适合重试

  • UNAVAILABLE:服务暂时不可用
  • DEADLINE_EXCEEDED:请求超时(需评估)
  • RESOURCE_EXHAUSTED:资源耗尽(需评估)

不适合重试

  • INVALID_ARGUMENT:参数错误
  • NOT_FOUND:资源不存在
  • PERMISSION_DENIED:权限不足
  • UNAUTHENTICATED:未认证

幂等性考虑

重试要求操作是幂等的:

  • 天然幂等:读取操作
  • 需要设计:写入操作使用幂等键
  • 非幂等:避免重试或使用 hedging

拦截器

客户端拦截器

客户端拦截器用于扩展客户端行为:

常见拦截器用途

日志记录

  • 记录请求方法、参数
  • 记录响应状态、耗时
  • 记录错误信息

认证注入

  • 自动添加认证 Token
  • Token 刷新和续期
  • 多租户标识注入

监控指标

  • 请求计数
  • 延迟统计
  • 错误率监控

断路器

  • 故障检测
  • 快速失败
  • 自动恢复

拦截器设计原则

与服务端拦截器类似,客户端拦截器也应遵循:

  1. 单一职责
  2. 轻量快速
  3. 正确的错误处理
  4. Context 传递

元数据传递

发送元数据

客户端可以在请求中添加元数据:

常见用途

用途示例键名说明
认证authorizationBearer Token
追踪x-trace-id分布式追踪
租户x-tenant-id多租户
请求IDx-request-id请求唯一标识
自定义x-*业务字段

接收元数据

服务端响应可能包含:

  • Header:响应头元数据
  • Trailer:尾部元数据(包含状态信息)

连接状态管理

监控连接状态

客户端可以监控 Channel 的连接状态:

IDLE → CONNECTING → READY → (使用中) → IDLE → ...
↘ TRANSIENT_FAILURE ↗

状态监控用途

  • 健康检查
  • 故障告警
  • 流量切换

连接等待

WaitForReady 选项允许客户端等待服务端就绪:

  • 不立即失败,等待连接建立
  • 适用于服务启动顺序不确定的场景
  • 注意结合 Deadline 使用

流式调用

流的生命周期

服务端流

特点

  • 客户端发送一个请求
  • 服务端返回多个响应

处理要点

  • 循环接收直到 EOF
  • 处理接收错误
  • 可以提前取消

客户端流

特点

  • 客户端发送多个请求
  • 服务端返回一个响应

处理要点

  • 发送完成后调用结束方法
  • 等待服务端响应
  • 处理发送失败

双向流

特点

  • 双方独立读写
  • 全双工通信

处理要点

  • 读写可以并发进行
  • 需要协调读写逻辑
  • 注意资源管理

TLS/安全连接

TLS 模式

模式服务端证书客户端证书适用场景
明文开发环境
服务端 TLS生产环境(内部)
mTLS生产环境(敏感)

证书验证

服务端 TLS

  • 客户端验证服务端证书
  • 确保连接到正确的服务端
  • 防止中间人攻击

mTLS(双向 TLS)

  • 服务端也验证客户端证书
  • 客户端身份认证
  • 零信任网络基础

证书管理

  • 使用 CA 签发的证书
  • 定期轮换证书
  • 证书吊销处理
  • 证书热加载

多语言实现对比

Stub 创建方式

语言同步 Stub异步 Stub
Go直接返回值无(使用 Goroutine)
JavanewBlockingStub()newStub()
Python同步方法async 方法
C++同步方法Async 方法
Node.js无(回调/Promise)无(回调/Promise)
.NET同步方法异步方法

Channel 配置

各语言的配置方式略有不同:

语言配置方式
GoDialOption 函数选项
JavaManagedChannelBuilder
Pythonoptions 参数
C++ChannelArguments
Node.js选项对象
.NETGrpcChannelOptions

错误处理

语言错误类型状态码获取
Goerrorstatus.Statusstatus.Code(err)
JavaStatusRuntimeExceptione.getStatus().getCode()
Pythongrpc.RpcErrore.code()
C++grpc::Statusstatus.error_code()
Node.jsError 对象err.code
.NETRpcExceptionex.StatusCode

最佳实践总结

连接管理

  1. 复用 Channel:应用生命周期内共享
  2. 正确关闭:应用退出时关闭 Channel
  3. 状态监控:监控连接健康状态
  4. 连接预热:启动时预先建立连接

超时控制

  1. 始终设置超时:避免请求无限等待
  2. 使用 Deadline:而非简单的 Timeout
  3. 区分操作类型:读写使用不同超时
  4. 传播 Deadline:调用链自动传播

错误处理

  1. 正确解析错误:使用标准错误码
  2. 区分错误类型:客户端错误 vs 服务端错误
  3. 合理重试:只重试可重试的错误
  4. 日志记录:记录详细错误信息

性能优化

  1. 连接复用:避免频繁创建连接
  2. 并发调用:充分利用多路复用
  3. 流式传输:大数据使用流式
  4. 压缩传输:启用消息压缩

可靠性保障

  1. 重试策略:配置合理的重试
  2. 断路器:防止故障扩散
  3. 降级处理:服务不可用时的备选方案
  4. 监控告警:及时发现和处理问题

小结

本章从概念层面介绍了 gRPC 客户端的核心架构:

  1. Stub 和 Channel:理解客户端的抽象层次
  2. 连接管理:Channel 的生命周期和复用
  3. 负载均衡:代理模式和客户端模式
  4. 超时与 Deadline:精确的时间控制
  5. 重试机制:声明式重试配置
  6. 拦截器:扩展客户端行为
  7. 元数据传递:请求头和尾部元数据
  8. 流式调用:三种流模式的处理
  9. 安全连接:TLS 和 mTLS 配置
  10. 多语言对比:不同语言的实现差异

具体语言的实现细节,请参考对应语言的开发章节:

  • Go 开发:go-dev.md
  • Python 开发:python-dev.md
  • Java 开发:java-dev.md
  • C++ 开发:cpp-dev.md
  • Node.js 开发:nodejs-dev.md
  • .NET 开发:dotnet-dev.md