gRPC0到1系列之【6】
文章目录
- 一、Stub核心
- 1.1 干什么是Stub?
- 1.2 Stub的类型(四种调用模式)
- 1.3 Stub的核心定位(远程服务器的本地代理)
- 1.4 Stub的主要类型与适用场景
- 1.4.1 阻塞式 Stub (Blocking Stub / Synchronous Stub)
- 1.4.2 异步 Stub (Asynchronous Stub / Non-blocking Stub)
- 1.4.3 流式 Stub (Streaming Stub)
- 1.5 Stub工作原理与生命周期
一、Stub核心
gRPC 的 Stub(存根) 是客户端与服务端之间通信的关键桥梁。它隐藏了底层网络调用、序列化、协议细节,使开发者能像调用本地函数一样调用远程服务。
1.1 干什么是Stub?
Stub(存根) 是 gRPC 客户端侧的代理对象,封装了对远程 gRPC 服务的调用逻辑。它由 Protocol Buffers 编译器(protoc
)配合 gRPC 插件自动生成,提供强类型、面向接口的 API。
✅ 核心作用:将“远程过程调用”伪装成“本地函数调用”。
类比理解一下:
- 传统 RPC:你需要手动构造请求、建立连接、发送字节、解析响应。
- gRPC Stub:你只需调用
client.SayHello(req)
,其余一切由 Stub 自动完成。
1.2 Stub的类型(四种调用模式)
gRPC 支持四种服务方法类型,对应四种 Stub 行为:
类型 | 描述 | Stub 行为 |
---|---|---|
Unary RPC | 一请求一响应 | 同步/异步函数调用 |
Server Streaming | 一请求多响应 | 返回流对象,循环读取 |
Client Streaming | 多请求一响应 | 发送多个请求后获取响应 |
Bidirectional Streaming | 多请求多响应 | 双向流,可并发读写 |
1.3 Stub的核心定位(远程服务器的本地代理)
在分布式系统中,客户端需要调用远程服务器上的方法。直接处理网络通信(如建立连接、发送数据、处理响应)是非常复杂且容易出错的。
gRPC Stub (客户端) 正是为了解决这个问题而存在的。它扮演了一个 “远程服务的本地代理” 角色。
- 对客户端应用来说:调用 Stub 的方法就像调用一个本地对象的方法一样简单、直观,完全感觉不到网络的存在。
- 对 gRPC 框架来说:Stub 负责将客户端的本地调用 “翻译” 成符合 gRPC 协议的网络请求,并将从网络接收到的响应 “翻译” 回客户端可以理解的返回值。
一句话总结:Stub 屏蔽了所有底层的网络通信细节,让远程调用像本地调用一样简单。
1.4 Stub的主要类型与适用场景
根据不同的通信模式,gRPC 提供了多种类型的 Stub。
1.4.1 阻塞式 Stub (Blocking Stub / Synchronous Stub)
- 特点:客户端调用方法后,当前线程会阻塞,直到收到服务端的响应或发生错误。
- 适用场景
- 简单的请求 - 响应模式。
- 业务逻辑简单,对响应时间不敏感,或者可以容忍线程阻塞的场景。
- 适合快速开发和调试。
- 示例:HelloServiceGrpc.newBlockingStub(channel)
1.4.2 异步 Stub (Asynchronous Stub / Non-blocking Stub)
-
特点:客户端调用方法后,线程立即返回,不会等待响应。结果通过回调函数 (Callback) 或 Future/Promise 对象来获取。
-
适用场景
- 需要高并发处理大量请求的场景。
- 不希望阻塞主线程(如 UI 线程、事件循环线程)的应用。
- 构建高性能、响应式的系统。
-
示例:HelloServiceGrpc.newFutureStub(channel) (Java) 或 HelloServiceGrpc.newStub(channel) (Java StreamObserver 模式)
1.4.3 流式 Stub (Streaming Stub)
流式 Stub 是基于异步 Stub 实现的,用于处理流式 RPC。
- 客户端流式 Stub:客户端可以连续发送多个请求消息,服务端在接收完所有消息后返回一个响应。
- 服务端流式 Stub:客户端发送一个请求,服务端可以连续返回多个响应消息。
- 双向流式 Stub:客户端和服务端可以像打电话一样,互相独立地发送一个消息流。
1.5 Stub工作原理与生命周期
✅ 工作原理(以一次 Unary RPC 为例)
- 创建 Call 对象:当客户端调用 Stub 的某个方法时(如
stub.sayHello(req)
),Stub 会首先创建一个Call
对象。Call
代表了一次完整的 RPC 调用。 - 封装请求:Stub 将传入的请求参数(
req
)使用 Protobuf 进行序列化,打包成 HTTP/2 的数据帧(Data Frame)。 - 设置元数据:Stub 将
Context
中携带的元数据(Metadata,如认证 Token、自定义 Header)也封装成 HTTP/2 的头帧(Header Frame)。 - 发送请求:Stub 将这些帧通过
Channel
发送给服务端。Channel
负责管理底层的 TCP 连接和 HTTP/2 会话。 - 等待响应
- 对于阻塞式 Stub,调用线程会进入等待状态,直到
Channel
接收到服务端的响应帧。 - 对于异步 Stub,调用线程立即返回,gRPC 框架会在后台监听响应。当响应到达时,通过回调或完成 Future 来通知应用。
- 对于阻塞式 Stub,调用线程会进入等待状态,直到
- 解析响应:
Channel
接收到响应帧后,将其传递给 Stub。Stub 使用 Protobuf 将二进制数据反序列化成响应对象。 - 返回结果:Stub 将最终的响应对象返回给客户端应用。
✅ 生命周期
- 创建:
Stub
实例通过 XXXGrpc.newXXXStub(channel)方法创建。创建时需要传入一个Channel
对象,Stub
的生命周期与它所绑定的Channel
紧密相关。 - 复用:
Stub
是无状态的,因此可以被安全地复用。创建Stub
的开销很小,但创建Channel
的开销较大。最佳实践是:创建一个共享的Channel
,并基于它创建多个Stub
,在整个应用程序生命周期中复用它们。 - 销毁:
Stub
本身没有需要手动释放的资源。当不再需要时,直接丢弃引用即可,由垃圾回收器(GC)处理。真正需要管理的是Channel
,必须在应用退出时调用channel.shutdown()
或channel.close()
来释放网络连接等资源。