1. 1. 从实战到原理:一文讲清 gRPC、Proto、Stub、Channel 与流式 RPC
    1. 1.1. 1. 为什么会有 gRPC
    2. 1.2. 2. gRPC 的核心组成部分
    3. 1.3. 3. Proto 是什么
    4. 1.4. 4. service、rpc、message 分别是什么
      1. 1.4.1. 4.1 service
      2. 1.4.2. 4.2 rpc
      3. 1.4.3. 4.3 message
    5. 1.5. 5. Proto 里的服务是怎么划分的
      1. 1.5.1. 5.1 按业务领域划分
      2. 1.5.2. 5.2 按职责边界划分
      3. 1.5.3. 5.3 注意:接口边界不一定等于部署边界
    6. 1.6. 6. gRPC 的调用链路:从 Proto 到真实网络调用
    7. 1.7. 7. Stub 和 Channel 是什么关系
      1. 1.7.1. 7.1 Channel 是什么
      2. 1.7.2. 7.2 Stub 是什么
      3. 1.7.3. 7.3 谁依赖谁
      4. 1.7.4. 7.4 代码示例
    8. 1.8. 8. 为什么 Channel 叫“逻辑连接”而不是真实连接
      1. 1.8.1. 8.1 为什么不是单一真实连接
      2. 1.8.2. 8.2 这和 I/O 多路复用是什么关系
    9. 1.9. 9. Interceptor 和 Transport 是什么
      1. 1.9.1. 9.1 Interceptor 是什么
      2. 1.9.2. 9.2 Transport 是什么
    10. 1.10. 10. 流式 RPC 是什么
    11. 1.11. 11. 四种 RPC 形态分别是什么
      1. 1.11.1. 11.1 Unary RPC
      2. 1.11.2. 11.2 Server Streaming RPC
      3. 1.11.3. 11.3 Client Streaming RPC
      4. 1.11.4. 11.4 Bidirectional Streaming RPC
    12. 1.12. 12. 为什么流式 RPC 有价值
    13. 1.13. 13. 从源码设计视角看,gRPC 比其他 RPC 好在哪
      1. 1.13.1. 13.1 IDL + Code Generation
      2. 1.13.2. 13.2 基于 HTTP/2
      3. 1.13.3. 13.3 框架能力更完整
      4. 1.13.4. 13.4 流控能力
      5. 1.13.5. 13.5 跨语言能力强
    14. 1.14. 14. 那为什么我平时只感觉自己在用 Stub + Channel
    15. 1.15. 15. 一个适合面试背诵的总述
    16. 1.16. 16. 一个适合面试背诵的简版问答
      1. 1.16.1. 16.1 Stub 和 Channel 的关系是什么?
      2. 1.16.2. 16.2 为什么 Channel 是逻辑连接,不是真实连接?
      3. 1.16.3. 16.3 Interceptor 是什么?
      4. 1.16.4. 16.4 流式 RPC 是什么?
      5. 1.16.5. 16.5 四种 RPC 分别是什么?
    17. 1.17. 17. 总结
    18. 1.18. 18. 一句话结论

从实战到原理:一文讲清 gRPC、Proto、Stub、Channel 与流式 RPC

从实战到原理:一文讲清 gRPC、Proto、Stub、Channel 与流式 RPC

这篇文章基于我前面对 gRPC 的一系列理解整理而成,目标不是只会背概念,而是尽量把 “平时怎么用”“底层为什么这样设计” 串起来。适合面试复习,也适合项目实践前快速建立整体认知。


1. 为什么会有 gRPC

在分布式系统里,服务和服务之间总要通信。
最原始的方式是自己基于 TCP 定协议,或者直接用 HTTP + JSON。

但这些方式都有一些共同问题:

  • 接口定义容易漂移
  • 客户端和服务端约束不够强
  • 序列化和反序列化效率一般
  • 流式通信、超时、取消、状态码这些能力要自己补
  • 跨语言协作时,文档和代码容易不一致

gRPC 的核心目标,就是把这些常见问题标准化。

你可以把它理解成:

gRPC 不只是一个“发请求的库”,更像一整套服务间通信基础设施。

它把下面这些能力打包到了一起:

  • 接口定义(Proto)
  • 代码生成(Stub / Service Skeleton)
  • 高效序列化(Protocol Buffers)
  • 传输协议(HTTP/2)
  • 长连接和多路复用
  • 流式 RPC
  • 超时、取消、状态码
  • 日志、鉴权、监控、拦截器等横切能力

所以 gRPC 的优势不只是“比 HTTP + JSON 快”,而是它把服务通信链路做成了一个统一模型


2. gRPC 的核心组成部分

如果从“写代码时最常接触什么”这个角度来看,gRPC 里最关键的几个词是:

  • proto
  • service
  • rpc
  • message
  • stub
  • channel
  • interceptor
  • transport

下面逐个解释。


3. Proto 是什么

proto 文件本质上是 接口定义文件
它用来描述:

  • 服务有哪些能力
  • 每个能力叫什么名字
  • 请求参数长什么样
  • 返回结果长什么样

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
syntax = "proto3";

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

这个文件表达的意思非常明确:

  • 定义了一个服务 Greeter
  • 它提供了一个远程方法 SayHello
  • 调用时传入 HelloRequest
  • 返回 HelloReply

你可以近似把它理解成下面这个 C++ 接口:

1
2
3
4
class Greeter {
public:
HelloReply SayHello(const HelloRequest& request);
};

只不过 gRPC 里的这个“函数”不是本地调用,而是远程调用


4. servicerpcmessage 分别是什么

4.1 service

service 表示一组远程能力的集合。

比如:

1
2
3
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

这里 Greeter 就是服务名。

它不是一个具体实例,而更像是:

  • 一组 RPC 的边界
  • 一个模块对外暴露的能力集合
  • 一个业务职责的通信接口

4.2 rpc

rpc 表示一个远程方法。

比如:

1
rpc SayHello (HelloRequest) returns (HelloReply);

意思就是:

  • 方法名叫 SayHello
  • 输入参数类型是 HelloRequest
  • 返回值类型是 HelloReply

4.3 message

message 表示消息结构,也就是请求体和响应体的数据定义。

例如:

1
2
3
message HelloRequest {
string name = 1;
}

这表示请求里有一个字段 name,类型是 string

所以:

  • service 像接口
  • rpc 像方法
  • message 像结构体 / DTO

5. Proto 里的服务是怎么划分的

这个问题很容易被问到。

很多人会觉得 service 只是随便起个名字,其实不是。

Proto 里定义一个 service,本质上是在划分一组远程能力的边界。

一般来说,service 不是按“代码文件”来划,而是按下面几个维度来划。

5.1 按业务领域划分

例如:

  • UserService
  • OrderService
  • PaymentService
  • ChatService

比如:

1
2
3
4
5
6
7
8
9
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserReply);
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserReply);
}

service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderReply);
rpc QueryOrder(QueryOrderRequest) returns (QueryOrderReply);
}

这就是最典型的按业务领域划分。

5.2 按职责边界划分

如果是一个聊天系统,可以进一步按职责拆:

  • AuthService:登录、鉴权、Token 校验
  • StatusService:在线状态、节点分配
  • MessageService:消息发送、拉取、送达确认
  • FileService:文件分片上传、断点续传、下载

这种划分的好处是:

  • 边界清晰
  • 便于维护
  • 更利于扩展和治理

5.3 注意:接口边界不一定等于部署边界

这一点很重要。

Proto 里的 service 更偏向接口边界,不一定严格等于一个物理进程。

也就是说:

  • 一个进程里可以注册多个 gRPC service
  • 一个 service 未来也可能被拆出去独立部署

所以更准确的理解是:

service 定义的是能力边界,不是进程边界。


6. gRPC 的调用链路:从 Proto 到真实网络调用

很多人一开始会觉得 gRPC 很神奇:

1
2
auto stub = Greeter::NewStub(channel);
stub->SayHello(...);

看起来就像在调本地函数。

但实际上,底层经过了很多层。

一个更完整的视角是:

1
业务代码 -> stub -> channel -> interceptor -> transport -> 远端服务

当然,平时你最常直接接触到的,主要还是 stub + channel


7. Stub 和 Channel 是什么关系

这是使用 gRPC 时最核心的一组概念。

7.1 Channel 是什么

channel 可以理解成:

到某个 gRPC 服务的逻辑通信通道。

比如:

1
2
3
4
auto channel = grpc::CreateChannel(
"localhost:50051",
grpc::InsecureChannelCredentials()
);

这个 channel 代表:

  • 我要访问哪个地址
  • 用什么凭证
  • 后续请求都通过这个逻辑通道发出去

7.2 Stub 是什么

stub 是基于 channel 创建出来的客户端代理对象

例如:

1
auto stub = Greeter::NewStub(channel);

它的意思是:

  • 我现在拿到了一个 Greeter 服务的客户端代理
  • 这个代理底层通过 channel 去和远端服务通信

所以:

  • channel 是通道
  • stub 是代理

7.3 谁依赖谁

stub 不能脱离 channel 单独工作。

更准确地说:

先有 channel,再基于 channel 创建 stub,最后通过 stub 调用远程方法。

关系图如下:

1
业务代码 -> stub -> channel -> 远端 gRPC 服务

7.4 代码示例

下面是一段典型的 C++ 客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "helloworld.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;

class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(Greeter::NewStub(channel)) {}

std::string SayHello(const std::string& name) {
HelloRequest request;
request.set_name(name);

HelloReply reply;
ClientContext context;

Status status = stub_->SayHello(&context, request, &reply);

if (status.ok()) {
return reply.message();
} else {
return "RPC failed";
}
}

private:
std::unique_ptr<Greeter::Stub> stub_;
};

int main() {
auto channel = grpc::CreateChannel(
"localhost:50051",
grpc::InsecureChannelCredentials()
);

GreeterClient client(channel);

std::string reply = client.SayHello("Alice");
std::cout << "Server reply: " << reply << std::endl;

return 0;
}

这段代码里的角色非常清楚:

  • channel:逻辑通信通道
  • stub_:客户端代理
  • stub_->SayHello(...):真正发起 RPC 调用

8. 为什么 Channel 叫“逻辑连接”而不是真实连接

这是一个很容易被追问的问题。

很多人学到这里会问:

既然 Channel 是连接,那为什么说它是逻辑连接,不是真实连接?

答案是:

因为 Channel 不等于“恰好一条底层 TCP 连接”。

它是 gRPC 暴露给应用层的通信抽象。

你在业务代码里不需要关心:

  • 底层现在有几条真实连接
  • 是否已经建立
  • 是否需要重连
  • 是否做了连接复用
  • 是否有多个并发 stream 在跑

这些都被封装在 channel 背后了。

8.1 为什么不是单一真实连接

因为底层基于 HTTP/2。

而 HTTP/2 的重要特性之一就是:

一条连接上可以并发多个 stream。

也就是说:

  • 同一个 channel 下可以复用一条 HTTP/2 连接跑很多 RPC
  • 某些实现或场景下,也可能对应多条真实连接
  • 所以 channel 更像“面向应用层的通信通道”,而不是某个具体 socket 的名字

8.2 这和 I/O 多路复用是什么关系

你提到 select / poll / epoll,这个联想是对的,但层级不同。

  • select / poll / epollOS 层的 I/O 事件通知机制
  • HTTP/2 stream multiplexing 是 协议层的多路复用

两者不是同一个概念。

可以类比,但不能混为一谈。

更准确地说:

  • gRPC 底层最终当然会依赖系统网络 I/O
  • 但你看到 channel 是逻辑连接,核心原因是 gRPC 把 HTTP/2 连接、stream、状态和配置都封装成了一个更高层的抽象

所以面试时可以这样说:

Channel 不是简单等于一个 socket,而是面向应用层的逻辑通信通道;底层基于 HTTP/2,一条连接上又可以复用多个 stream,因此不能简单把 channel 理解成一条物理 TCP 连接。


9. Interceptor 和 Transport 是什么

这是很多人第一次看 gRPC 结构时会感到抽象的地方。

因为平时业务代码里最直接接触的通常是:

  • stub
  • channel

interceptortransport 往往不是天天手写。

9.1 Interceptor 是什么

interceptor 可以理解成:

RPC 调用链上的中间件 / 过滤器。

它适合做那些和具体业务方法无关,但对很多 RPC 都通用的逻辑,比如:

  • 自动打日志
  • 自动注入 token / metadata
  • 统计 RPC 耗时
  • 统一错误处理
  • tracing / metrics

如果不使用 interceptor,那这些逻辑就得在每个 RPC 调用前后手写,很容易散落到各处。

所以:

interceptor 的价值在于把横切逻辑统一收口。

9.2 Transport 是什么

transport 是更底层的一层。

它负责真正的网络传输,比如:

  • 建立 HTTP/2 连接
  • 管理 stream
  • 收发 frame
  • 缓冲数据
  • 执行流控
  • 和 OS / socket 交互

这层一般业务开发不会直接操作。

所以可以这样记:

  • stub:给你本地函数一样的调用接口
  • channel:到远端服务的逻辑通道
  • interceptor:在调用链路上做通用增强
  • transport:最底层真正负责网络传输

10. 流式 RPC 是什么

很多人对 RPC 的第一印象是:

  • 发一个请求
  • 回一个响应

这确实是最常见的形式,但 gRPC 不止支持这种模式。

所谓 流式 RPC(Streaming RPC),就是指:

一次 RPC 调用里,不一定只有一条请求和一条响应,而是可以是多条消息组成的流。

gRPC 一共支持四种 RPC 形态。


11. 四种 RPC 形态分别是什么

你可以把它们记成一张表:

  • Unary:1 -> 1
  • Server Streaming:1 -> N
  • Client Streaming:N -> 1
  • Bidirectional Streaming:N -> N

下面逐个解释。

11.1 Unary RPC

这是最普通的方式。

  • 客户端发 1 条请求
  • 服务端回 1 条响应

就像普通函数调用。

典型场景:

  • 查询用户信息
  • 登录
  • 查询订单详情

比如:

1
rpc SayHello (HelloRequest) returns (HelloReply);

这就是一个 Unary RPC。

11.2 Server Streaming RPC

  • 客户端发 1 条请求
  • 服务端连续返回 多条响应

适合“我问一次,你持续回复多条结果”的场景。

典型场景:

  • 查询任务进度
  • 拉取日志流
  • 搜索结果逐条返回

可以理解成:

客户端只发一次请求,服务端一直往外推消息。

11.3 Client Streaming RPC

  • 客户端连续发送 多条请求
  • 服务端最后返回 1 条响应

适合“我先把一批数据传给你,你处理完后再给我一个总结果”的场景。

典型场景:

  • 文件分片上传
  • 批量指标上报
  • 批量写日志

你可以理解成:

客户端像在“流式上传”,服务端最后统一给个结果。

11.4 Bidirectional Streaming RPC

  • 客户端连续发 多条请求
  • 服务端也连续回 多条响应
  • 双方可以边发边收,彼此独立

这是最像“实时对话”的一种模式。

典型场景:

  • 聊天系统
  • 实时推送
  • 游戏状态同步
  • 流式模型推理

可以理解成:

两边都在持续说话,也都在持续听。


12. 为什么流式 RPC 有价值

如果没有流式 RPC,很多场景就只能自己在应用层想办法拼装协议,或者不停发很多次普通请求。

有了流式 RPC,很多问题会自然很多:

  • 进度推送:更适合 server streaming
  • 分片上传:更适合 client streaming
  • 聊天和实时交互:更适合 bidirectional streaming

这其实也是 gRPC 相比很多“普通 HTTP 接口”更有优势的地方。

因为流式通信在 gRPC 里不是额外 hack 出来的,而是框架的一等公民。


13. 从源码设计视角看,gRPC 比其他 RPC 好在哪

如果只说一句“gRPC 更快,因为 Protobuf + HTTP/2”,那太浅。

更深一点的理解应该是:

gRPC 的优势不是单点优化,而是把服务通信的一整套基础设施标准化了。

13.1 IDL + Code Generation

先用 .proto 定义接口,再自动生成客户端和服务端代码。

好处是:

  • 接口强约束
  • 多语言一致
  • 编译期就能发现很多问题
  • 不容易出现“文档和实现漂移”

13.2 基于 HTTP/2

HTTP/2 给 gRPC 提供了:

  • 长连接
  • 多路复用
  • Stream 抽象
  • 更适合流式通信

所以它不只是“连接复用更好”,而是协议层就天然更适合高并发服务调用。

13.3 框架能力更完整

gRPC 内建或良好支持这些生产环境能力:

  • deadline / timeout
  • cancellation
  • status code
  • interceptor
  • authentication
  • tracing
  • health check
  • load balancing
  • flow control

这意味着它不是只解决“怎么发包”,而是把工程落地时真正会碰到的服务治理问题一起考虑了。

13.4 流控能力

特别是在 streaming 场景下,流控很重要。

gRPC 框架会帮助处理:

  • 对端消费不过来怎么办
  • 缓冲区管理
  • 发送节奏控制

如果自己基于 TCP 或 HTTP 长连接封装一个类 RPC 协议,这部分工作往往要自己处理。

13.5 跨语言能力强

只要多种语言都遵守同一个 .proto,就可以生成各自语言风格的 stub / skeleton。

这在多语言微服务体系里非常有价值。


14. 那为什么我平时只感觉自己在用 Stub + Channel

这是很正常的。

因为 gRPC 的设计目标之一就是:

让业务开发者尽量站在更高抽象层工作。

所以你平时最直接感知到的是:

  1. proto
  2. 生成代码
  3. 创建 channel
  4. 基于 channel 创建 stub
  5. stub->Method(...)

而下面这些层:

  • interceptor
  • transport
  • HTTP/2 frame
  • stream 管理
  • 连接状态
  • 流控

都被框架藏起来了。

这不是你“没用到”,而是你没有显式手写它们

更准确地说:

  • transport 你几乎一定在用,只是由框架接管
  • interceptor 你只有在需要日志、鉴权、监控等统一逻辑时,才会显式配置

15. 一个适合面试背诵的总述

如果面试官问你:

你理解的 gRPC 是什么?

可以这样回答:

gRPC 是一套基于 Proto 和 HTTP/2 的远程过程调用框架。它先用 .proto 定义服务和消息结构,再通过代码生成得到客户端 stub 和服务端接口骨架。业务代码里通常是基于 channel 创建 stub,再通过 stub 像调用本地函数一样发起远程调用。底层则由框架负责 HTTP/2 连接、stream、多路复用、流控、超时取消、状态码以及认证监控等能力。相比普通 HTTP+JSON 或很多自研 RPC,gRPC 的优势不只是性能更高,还在于接口强约束、跨语言一致、流式 RPC 支持更好,并且通信基础设施更完整。


16. 一个适合面试背诵的简版问答

16.1 Stub 和 Channel 的关系是什么?

Channel 是到远端 gRPC 服务的逻辑通信通道,Stub 是基于 Channel 创建出来的客户端代理对象。业务代码不是直接用 Channel 调方法,而是先创建 Stub,再通过 Stub 调具体的 RPC。

16.2 为什么 Channel 是逻辑连接,不是真实连接?

因为 Channel 不等于一条固定 TCP 连接。gRPC 底层基于 HTTP/2,一个 Channel 可以复用一条或多条连接,而连接上又可以并发多个 Stream,所以它更像应用层的通信通道抽象,而不是某个具体 Socket。

16.3 Interceptor 是什么?

Interceptor 就像 RPC 层的中间件,适合做日志、鉴权、Metrics、Metadata 注入等和具体业务方法无关的通用逻辑。

16.4 流式 RPC 是什么?

流式 RPC 指的是一次 RPC 调用里不一定只有一条请求和一条响应,而是可以传多条消息。gRPC 支持 Unary、Server Streaming、Client Streaming、Bidirectional Streaming 四种模式。

16.5 四种 RPC 分别是什么?

  • Unary:1 -> 1
  • Server Streaming:1 -> N
  • Client Streaming:N -> 1
  • Bidirectional Streaming:N -> N

17. 总结

如果只看表面,gRPC 像是“把远程调用包装成了本地函数调用”。

但如果往下看,会发现它背后是一整套很完整的通信设计:

  • Proto 负责契约
  • Stub 负责代理
  • Channel 负责逻辑通信通道
  • Interceptor 负责横切逻辑
  • Transport 负责真正的网络传输
  • HTTP/2 负责长连接、多路复用和流式能力

所以真正值得记住的,不是“gRPC 比别的快”这么一句话,
而是:

gRPC 把服务间通信从“手写协议和调用细节”提升成了一套标准化工程能力。

这也是它在现代分布式系统里这么常见的原因。


18. 一句话结论

Proto 定义契约,Stub 发起调用,Channel 提供通道,HTTP/2 承载传输,Streaming 扩展交互方式,gRPC 最核心的价值是把服务通信做成了一套标准化基础设施。