跳到主要内容
约 14 分钟7 张卡

gRPC

gRPC 是一种远程调用框架,用服务定义约定方法和消息类型,再由生成的客户端与服务端代码完成调用。标准 gRPC 通常使用 Protobuf 描述和编码消息,通过 HTTP/2 承载请求、响应和调用状态。

它把许多通信工作放进了框架,但远程调用仍然会失败、超时或只执行了一部分。准备这个主题时,既要解释各层协议的作用,也要能判断:收到一条消息是否代表整个调用成功,取消是否真的让服务端工作停止,重试会不会重复修改业务数据。

1. 面试核心​

先记住这几条,再读正文
  • 服务定义描述方法与消息,Protobuf 编码消息,HTTP/2 承载通信。gRPC、Protobuf 和 HTTP/2 处在不同层次,不能互相替代。卡 · 协议与接口契约
  • 一次 RPC 调用尝试通常对应一个 HTTP/2 stream,连接可以复用多个 stream。gRPC 消息有自己的长度前缀,不与 HTTP/2 DATA 帧一一对应。
  • gRPC 支持一元、服务端流、客户端流和双向流。流内保持对应方向的消息顺序,双向流的两个方向可以独立推进。卡 · 四种调用形式
  • metadata 传递调用附加信息,最终状态说明 RPC 结果。HTTP 200 或已经收到消息,都不等于最终 gRPC 状态为 OK。卡 · metadata 与最终状态
  • deadline 限制调用的等待预算,传给下游时应扣除已用时间。超时只说明调用未在预算内成功结束,不能证明业务没有发生。卡 · 剩余预算的传递
  • 取消通知需要处理器配合。客户端已取消、服务端已退出、业务已回滚是三个不同的事实。卡 · 取消通知与处理器退出
  • 流控限制传输中的数据,不会替应用限制全部队列和工作线程。阻塞操作需要可结束的等待和明确的资源清理。卡 · 流控与资源退出
  • 比较方案时,应把 gRPC 与具体的 HTTP JSON 等接口方案比较。HTTP/2、二进制编码或代码生成都不能单独证明某个服务更快。卡 · 方案比较

2. 协议与接口契约​

2.1 各层分别负责什么​

接口描述语言(IDL)描述远程服务的方法、参数和结果类型。gRPC 默认采用 Protobuf 的 .proto 定义,生成的 stub 是调用方访问远程方法的本地代理;服务端则实现生成代码规定的方法接口。

部分负责的事情不会自动解决什么
服务定义服务名、方法名、请求与响应类型、是否为流业务权限、方法是否允许重复执行
Protobuf消息结构与二进制编码连接管理、业务事务
gRPC 调用协议与运行库方法分派、消息封装、metadata、状态、超时与取消应用代码的全部退出、外部操作回滚
HTTP/2连接内多路复用、帧和流量控制等无界业务队列、跨调用的业务顺序

Protobuf 字段编号、未知字段和协议演进属于序列化契约,详见 RPC。新增字段通常比复用旧编号更容易兼容,但兼容仍需检查字段含义、缺省值及旧新版本的混合通信,不能只看生成代码是否成功。

gRPC 也能使用其他消息编码方式,不能把“默认用 Protobuf”说成“只能发送 Protobuf”。下面按 gRPC over HTTP/2 与 Protobuf 的组合说明,协议依据为 gRPC 1.71.0 仓库文档。

2.2 一次调用在连接上怎样表示​

标准 gRPC over HTTP/2 用 POST 发起调用,路径包含完整服务名和方法名,例如 /demo.Echo/Unary。请求头包含 application/grpc 类型、调用 metadata,以及设置了截止时间时的 grpc-timeout 等信息。

一次 RPC 调用尝试通常使用一个 HTTP/2 stream。发生重试时,新的尝试可以使用新的 stream。客户端的 channel 是逻辑通信入口,可以管理连接和解析结果,不等于“一次调用新建一条 TCP 连接”,也不能保证一个 channel 永远只有一条物理连接。

消息体外面还有 gRPC 的长度前缀:

1 字节压缩标记 | 4 字节消息长度(大端) | 消息字节

接收方据此前缀识别完整消息。一条消息可以跨多个 HTTP/2 DATA 帧,多个较小消息也可以出现在同一帧中,因此不能把一次网络读取或一个 DATA 帧当作一条业务消息。HTTP/2 的多路复用与连接约束见 HTTP。

3. 四种调用形式​

gRPC 的流是同一次 RPC 内的一系列消息。调用形式由请求方向和响应方向是否允许多条消息决定:

形式请求 → 响应可以怎样使用
一元调用(unary)一条 → 一条查询一个对象、提交一次操作
服务端流一条 → 多条读取一组结果或持续接收更新
客户端流多条 → 一条连续提交若干记录,最后取得汇总
双向流多条 → 多条两端持续交换消息,按业务约定关联结果

下面的定义可以生成四类方法。示例消息只包含文本和数量,用于观察调用形态:

syntax = "proto3";
package demo;

message Message {
string text = 1;
int32 count = 2;
}

service Echo {
rpc Unary(Message) returns (Message);
rpc Expand(Message) returns (stream Message);
rpc Collect(stream Message) returns (Message);
rpc Chat(stream Message) returns (stream Message);
}

stream 位于请求类型前,表示客户端发送消息流;位于响应类型前,表示服务端发送消息流。它只说明允许的消息形式,不能单靠这个声明决定“收到一条就回复一条”还是“先收齐再处理”。

同一次 RPC 中,每个方向的消息顺序得到保持,但双向流没有自动的一问一答配对关系。如果业务需要知道某条回复对应哪个请求,应在消息契约中设计关联标识。不同 RPC 之间也不保证业务执行顺序。

客户端结束发送,相当于声明请求方向已经结束,服务端仍可以继续发送响应。客户端流的服务端如果需要收齐再计算,就必须等到这个结束信号;双向流若双方都等待对方先结束,再开始读取或回复,就可能互相等待。

4. metadata 与最终状态​

4.1 调用附加信息​

metadata 是随 RPC 传递的键值信息,适合携带请求关联号、认证信息或追踪上下文。它位于消息之外,不需要把每一个调用层字段都加进业务消息。

请求可以带 metadata,响应可以带初始 metadata 和结束时的 trailing metadata。键名通常使用小写;二进制 metadata 的键以 -bin 结尾,由运行库处理对应编码。应用不要占用保留的 grpc- 前缀,也不应把大量文件内容放进 metadata。

收到一个 authorization 字段,不代表调用已经通过授权。服务端仍需验证凭据和资源权限;metadata 只是传递信息的载体。真实网络部署还要按需求配置 TLS 与服务身份,本地实验中的明文连接仅用于回环环境。

4.2 HTTP 状态与 gRPC 状态​

gRPC status 表示这次 RPC 最终怎样结束。正常协议响应通过 grpc-status 传递结果,0 代表 OK;发生立即失败时,也可以使用只有尾部信息的响应,而没有业务消息。

状态主要含义调用方需要进一步判断什么
OKRPC 按协议成功结束业务契约是否还在消息中定义其他结果
INVALID_ARGUMENT参数不满足要求修正请求,重复原请求通常无效
UNAUTHENTICATED / PERMISSION_DENIED身份未通过 / 权限不足凭据、主体与资源授权
DEADLINE_EXCEEDED截止时间内未完成远端是否已经产生结果或副作用
CANCELLED调用被取消服务端工作是否响应了取消
UNAVAILABLE服务暂时无法完成调用等可用性问题是否适合重试,以及重试是否安全

一个 HTTP 200 响应可以携带非 OK 的 gRPC status。代理返回普通 HTML 错误页或缺少 gRPC 状态时,客户端运行库还需要将其转换为调用错误,不能按普通 HTTP 页面处理。

流式调用尤其需要读到最终状态。服务端可能先发出两条结果,再因内部错误结束;前两条消息真实存在,但整个 RPC 并未成功。应用要按契约决定能否保留部分结果,不能在收到第一条消息时就宣布整批完成。

下面使用 grpcio / grpcio-tools 1.71.0、Protobuf 5.29.5。将前面的定义保存为 demo.proto 并生成 demo_pb2.py 和 demo_pb2_grpc.py 后,客户端向实现了 Unary 回显的本地服务发起调用:

import grpc
import demo_pb2
import demo_pb2_grpc

with grpc.insecure_channel("127.0.0.1:50051") as channel:
stub = demo_pb2_grpc.EchoStub(channel)
reply, call = stub.Unary.with_call(
demo_pb2.Message(text="hello", count=1),
timeout=1.0,
metadata=(("request-id", "demo-1"),),
)
print(reply.text, reply.count)
print(call.code().name)

成功时输出 hello 1 和 OK。timeout=1.0 设置本次 RPC 的等待预算,with_call 同时返回消息与调用对象;非 OK 状态会通过 RpcError 进入错误处理。这个客户端不能独立替代服务端,生成代码与两端实现仍需使用一致的服务契约。

5. deadline 与取消​

5.1 剩余预算的传递​

deadline 是调用最晚应结束的时刻;有些语言 API 用 timeout 接收允许持续的时间,再转换为截止时间。没有设置 deadline 时,调用可能等待很久,因此应根据整体时限给每次调用设置合理预算。

假设上游总预算为 1 秒,本地已经处理了 700ms,下游就不应重新获得完整 1 秒。向下游发起调用时,应传递剩余预算,并为返回和清理留出需要的时间。gRPC 在协议上使用剩余超时,避免直接依赖不同机器的绝对时钟完全一致。

自动传播是否生效取决于语言实现和调用方式,不能默认“所有下游调用都会继承”。应检查所用实现的上下文规则,并验证没有丢失 deadline。Go 的具体 API 和上下文用法属于语言专项。

客户端得到 DEADLINE_EXCEEDED,说明它没有在预算内取得成功结果。服务端可能尚未开始,也可能已经提交数据库事务,只是响应来晚了。需要重试的写操作应配合幂等键与结果查询。

5.2 取消通知与处理器退出​

取消通知调用链:这次调用已经不需要继续等待。客户端主动取消、deadline 到期或通信异常,都可能使调用结束,但运行库通常不能直接打断任意业务代码。

以同步 Python gRPC 处理器为例,可以通过上下文查询调用是否仍有效,或登记结束回调来通知正在等待的工作。回调表示调用已经终止,不只在主动取消时触发,因此不能把它当作“用户撤销业务”的唯一依据。

一个处理器若正在执行不响应取消的阻塞函数,即使客户端已经收到取消结果,它仍可能占着工作线程。应用应给外部调用设置时限、使用可被唤醒的等待,或者把工作放到具有明确终止协议的执行单元中;简单地把阻塞操作换到另一个线程,不会让这个线程自动停止。

资源释放应放在覆盖正常返回、异常和取消的清理路径中。对长循环要检查结束条件,对流式请求要在不再消费时取消或关闭对应调用,对派生的下游请求也要按实现传播取消。

5.3 取消不等于回滚​

取消改变的是调用状态,不会撤销已经提交的业务结果。例如服务端已经保存订单,随后客户端超时,订单仍然存在;取消也不保证一封已经交给外部邮件服务的邮件会被收回。

如果业务允许撤销,应设计独立的撤销或补偿操作,并检查当前业务状态。不能把传输层取消当作数据库事务回滚。结果不明时先按稳定业务身份核对,再决定重试、补偿或向用户返回处理中。

6. 流控与资源退出​

6.1 传输流控与应用容量​

流量控制限制发送方在接收方处理能力范围内传输数据。HTTP/2 有连接和 stream 级窗口,gRPC 运行库据此协调数据发送;具体读写 API 可能阻塞,也可能返回表示异步完成的对象。

写入 API 返回,不一定表示对端业务已经处理消息,数据可能只进入了框架缓冲。应用仍要限制生产速度、消息大小、在途数量和业务队列。否则,网络层虽然在限速,上游仍可能不断把待发送对象堆进内存。

双向流还要避免读写互相等待。比如双方都先连续写入大量数据、坚持写完才读,缓冲和窗口耗尽后就可能无法推进。应根据协议安排读取与发送,必要时让它们并行,但仍保证每个 API 所要求的并发使用约束。

6.2 关闭服务与结束工作​

服务关闭通常先停止接收新调用,再给已有调用一段完成时间,超过宽限期后终止剩余调用。这个过程解决 RPC 生命周期,但业务处理器仍需配合退出,不能依赖强制取消自动打断所有阻塞工作。

需要检查的不只是“客户端不再等待”,还包括处理器是否返回、下游调用是否结束、线程池能否回收,以及持有的文件和连接是否释放。如果清理本身需要等待,也应明确上限和失败后的处理方式。

在 grpcio 1.71.0 的同步服务实验中,服务停止事件完成时,不响应取消的处理器仍然停留在阻塞等待中;明确唤醒后,处理器才退出,线程池才能回收。因此,不能用传输停止信号代替业务工作已经结束的证据。

本文配套实验使用固定版本 Python gRPC 的真实客户端与服务端,验证四种调用、metadata 与尾部状态、部分响应后失败、超时、主动取消和阻塞处理器退出。阻塞示例通过受控事件明确释放后才能结束;本地 SQLite 提交后再触发超时,验证取消不会自动回滚已提交结果。实验只使用隔离容器的回环地址,不作为 TLS、代理兼容性、多主机故障或性能结论。

7. 方案比较​

gRPC 本身运行在 HTTP/2 之上,因此“gRPC 比 HTTP 好在哪里”需要先明确比较对象。更有意义的比较是:同一业务使用 gRPC 加 Protobuf,还是使用某种 HTTP JSON 接口,分别会带来哪些约束。

需要比较的方面需要说明的条件
接口契约是否需要多语言代码生成、明确消息类型和统一的方法签名
数据与传输消息大小、编码成本、连接复用、流式需求;HTTP JSON 也可以使用 HTTP/2
调试与生态现有网关、代理、监控和客户端是否理解 gRPC 的状态与流
浏览器调用浏览器不能直接按普通 Fetch 用法完成所有原生 gRPC 行为;需考虑 gRPC-Web 等适配方案及其能力限制
故障语义两种方案都要处理超时、幂等、限流、部分成功和业务恢复

二进制编码不必然让整个接口更快。数据库、业务计算、消息大小、压缩、网络往返和实现方式都会影响结果;应在等价条件下测量,而不能用协议名字代替性能证据。

8 · 面试题卡片​

本页题目与参考回答

8.1 gRPC 使用什么底层协议,Protobuf 在其中负责什么?​

考察重点:服务契约、消息编码与传输层次。

相关内容:协议与接口契约、RPC 的序列化协议。

参考回答

标准 gRPC 通常通过 HTTP/2 通信,默认用 Protobuf 描述接口并编码消息。 服务定义约定方法和消息类型,生成的 stub 把本地调用转换成远程请求;HTTP/2 提供连接内多路复用和帧传输等能力。

gRPC 消息还有压缩标记和长度前缀,消息边界不等于 DATA 帧边界。Protobuf 字段编号与编码解决消息表示,不负责网络连接或业务事务;gRPC 也可以采用其他编码方式。

8.2 gRPC 的四种调用形式有什么区别,双向流是否要求一问一答?​

考察重点:请求与响应的消息数量、顺序和结束条件。

相关内容:四种调用形式。

参考回答

四种形式分别是一条对一条、一条对多条、多条对一条和多条对多条。 双向流的两个方向独立,保持各自的消息顺序,但不自动形成一问一答配对。

业务需要关联请求与回复时自行设计标识。客户端结束发送后,服务端仍可继续响应;双方应约定何时读、何时写和何时结束,避免互相等待。

8.3 收到 HTTP 200 或一条响应消息,能否认定 gRPC 调用成功?​

考察重点:HTTP 与 gRPC 状态、metadata 和流的最终结果。

相关内容:metadata 与最终状态。

参考回答

不能,应该检查整个 RPC 的最终 gRPC status。 HTTP 200 可以携带非 OK 状态,流式调用也可能先发出若干消息,再以错误结束。

metadata 用于传递调用附加信息,结束时的 trailers 可以携带状态和附加信息。应用需要通过运行库处理最终状态,再按业务契约决定如何处理已经收到的部分结果。

8.4 上游只剩 200ms,为什么不能给下游重新设置 1 秒超时?​

考察重点:总时限、剩余预算与远端执行结果。

相关内容:剩余预算的传递。

参考回答

下游调用应服从上游剩余预算,否则上游已经结束,下游仍可能继续消耗资源。 传播时扣除已用时间,并检查语言实现是否自动继承上下文,避免在每一层重新计时。

超时只能说明调用未在预算内成功返回,不能说明远端没有提交业务。结果未知的写操作还需要幂等身份和查询机制。

8.5 客户端取消成功后,服务端线程和已提交事务会怎样?​

考察重点:调用终态、合作退出与业务副作用。

相关内容:取消通知与处理器退出、取消不等于回滚。

参考回答

客户端结束等待不保证服务端处理器已经退出,也不会回滚已提交事务。 处理器需检查取消状态、结束派生工作并释放资源;不响应取消的阻塞函数可能继续占用线程。

已提交的业务由业务状态决定。需要撤销时另行设计受约束的补偿或撤销操作,不能依靠传输层取消。

8.6 有 HTTP/2 流控后,还需要限制业务队列并处理阻塞退出吗?​

考察重点:传输窗口、应用缓冲与处理器生命周期。

相关内容:流控与资源退出。

参考回答

需要,传输流控不会限制应用自己创建的全部任务和缓冲。 发送受阻时,上游若继续生产,业务队列仍可能无限增长;还要限制消息大小、在途数量和生产速度。

服务关闭或调用取消后,应验证处理器和派生任务真正结束。阻塞等待需要时限或唤醒机制,把工作移到线程池也不能让它自动可取消。

8.7 怎样比较 gRPC 与 HTTP 接口方案?​

考察重点:比较层次、契约需求、客户端生态与性能条件。

相关内容:方案比较。

参考回答

先明确 gRPC 本身使用 HTTP/2,再把它与具体的 HTTP JSON 等接口方案比较。 可以从代码生成、消息契约、流式需求、客户端和代理支持、调试方式等方面判断是否合适。

HTTP JSON 也可以复用连接和使用 HTTP/2,双方也都需要处理超时、幂等和业务失败。性能应在等价消息、业务和网络条件下测量,不能仅凭 Protobuf 或 HTTP/2 推断所有场景都更快。

9. 参考资料​

来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明