Skip to content

Commit 55baa4a

Browse files
committed
Add post: ruapc 02 refactor
1 parent 07bbdae commit 55baa4a

File tree

2 files changed

+46
-15
lines changed

2 files changed

+46
-15
lines changed

posts/build_a_rust_rpc_framework_from_scratch/[2025.07.19 Rust,RuaPC,Hidden]01.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 从零开始构建 Rust RPC 框架「一、基本框架
1+
# 从零开始构建 Rust RPC 框架「一、星辰大海
22

33
### 1. 星辰大海
44

@@ -8,11 +8,12 @@ Rust 生态里,RPC 框架的关注度似乎不高,Web 框架倒是有不少
88
2. 序列化协议使用 JSON 和 [MessagePack](https://msgpack.org/),数据包含 schema 能自解释;
99
3. 传输层协议支持 TCP、WebSocket 和 RDMA,支持前端页面调用 RPC;
1010
4. 高性能、低延迟,优先优化 RDMA 网络场景;
11-
5. 支持 RPC 回调,server 可以调用 client 提供的方法;
12-
6. 完善的可观测性,提供充足的 metrics 和 logs,支持分布式 tracing;
13-
7. 丰富的预定义 service,例如查询 server stats,开箱可用;
14-
8. [可选] 支持网页 API 文档和接口调试工具,支持 JSON Schema;
15-
9. [可选] 支持 Python 接口绑定。
11+
5. 支持反向 RPC,server 可以调用 client 提供的方法;
12+
6. 支持反射,server 能否向 client 暴露和描述自身所提供的服务和方法;
13+
7. 完善的可观测性,提供充足的 metrics 和 logs,支持分布式 tracing;
14+
8. 丰富的预定义 service,例如查询 server stats,开箱可用;
15+
9. [可选] 支持网页 API 文档和接口调试工具,支持 JSON Schema;
16+
10. [可选] 支持 Python 接口绑定。
1617

1718
以上这些目标我不确定能否都实现。我会在这个系列的博文里详细介绍我的想法和设计。为了避免烂尾,这个系列的博文我会设置为隐藏,等主要目标都实现了我再一次性公开。
1819

@@ -82,7 +83,7 @@ impl EchoService for DemoImpl {
8283
}
8384
```
8485

85-
当 Server 端收到一条包含调用信息和参数序列化结果的请求时,如何调用对应的方法呢?这里还需要生成一个方法名到调用函数的映射。ruapc 中基于 tokio 和 [return type notation](https://rust-lang.github.io/rfcs/3654-return-type-notation.html),使用宏为 `trait` 生成 `ruapc_export` 方法
86+
当 Server 端收到一条包含调用信息和参数序列化结果的请求时,如何调用对应的方法呢?这里还需要生成一个方法名到调用函数的映射。ruapc 中基于 tokio 和 [return type notation](https://rust-lang.github.io/rfcs/3654-return-type-notation.html),使用宏为 `trait` 生成 `ruapc_export` 方法来实现 dispatch
8687

8788
```rust
8889
pub type Method = Box<dyn Fn(Context, RecvMsg) -> Result<()> + Send + Sync>;
@@ -95,7 +96,7 @@ pub trait EchoService {
9596
) -> ::std::collections::HashMap<String, ruapc::Method>
9697
where
9798
Self: 'static + Send + Sync,
98-
Self(..): Send,
99+
Self::echo(..): Send,
99100
{
100101
let mut map = ::std::collections::HashMap::<String, ruapc::Method>::default();
101102
let this = self.clone();
@@ -128,11 +129,10 @@ pub trait EchoService {
128129

129130
1. 我并不希望宏生成任何新的 `struct` 类型;
130131
2. 我喜欢所见即所得,我并不希望宏生成的函数篡改了函数声明;
131-
3. 我希望在 client 调用时,保持完全一致函数签名,所有要求返回值一定是一个 `Result<T, E>`,并且要求 `ruapc::Error` 可以转为用户声明的 `Error` 类型;
132-
4. Rust 目前(2024 edition,1.88)对 async trait dynamic dispatch 的支持依然不够好。可以依赖 [dynosaur](https://crates.io/crates/dynosaur) 实现,但我觉得还不够好用;
133-
5. 最终使用 `ruapc_export` 将所有异步方法导出为同步方法 + `tokio::spawn`,是我能想到的可行且能接受的唯一方案了。与 tokio 强绑定对我来说并不是一个问题,毕竟它基本也是事实标准;
134-
6. 使用 return type notation 是为了满足 `tokio::spawn` 的需求。该特性目前还不是稳定的状态,所以整个项目都必须依赖 nightly 版本的 Rust。这对我来说不是一个问题,不过还是希望在 ruapc 完成前这个特性可以 Stable;
135-
7. 只有确定了具体的方法,才能对参数进行对应类型的反序列化。
132+
3. 我希望在 client 调用时,保持完全一致函数签名。因为 RPC 一定是有概率失败的,所有要求返回值一定是一个 `Result<T, E>`,并且要求 `ruapc::Error` 可以转为用户声明的 `Error` 类型;
133+
4. 我希望拿到 context 时,client 就可以发起调用,在 server 端处理请求时获得的 context 亦可发起调用,也就能实现非常直觉的反向 RPC;
134+
5. Rust 目前(2024 edition,1.88)对 async trait dynamic dispatch 的支持依然不够好。可以依赖 [dynosaur](https://crates.io/crates/dynosaur) 实现,但我觉得还不够好用。最终使用 `ruapc_export` 将所有异步方法导出为同步方法 + `tokio::spawn`,是我能想到的可行且能接受的方案了。与 tokio 强绑定对我来说并不是一个问题,毕竟它基本也是事实标准;
135+
6. 使用 return type notation 是为了满足 `tokio::spawn` 的需求。该特性目前还不是稳定的状态,所以整个项目都必须依赖 nightly 版本的 Rust。这对我来说不是一个问题,不过还是希望在 ruapc 完成前这个特性可以稳定下来。
136136

137137
在 Client 端,我们希望可以直接调用 `EchoService::echo` 方法触发 RPC 请求。也就是说 `Client` 端需要有一个默认的 `EchoService` 实现,它实际上会完成序列化、发送、接收、反序列化的步骤。我们使用 Rust 的宏实现这一目的,详情见[代码](https://github.com/SF-Zhou/ruapc/blob/b64248314de3eacfcbf2d6ab1f3ec5f7ad6a3edf/ruapc-macro/src/lib.rs),宏会帮我生成如下的代码:
138138

@@ -144,7 +144,7 @@ impl EchoService for ruapc::Client {
144144
}
145145
```
146146

147-
生成的代码将方法名、请求类型和返回类型(依赖推导)传递给 `ruapc_request` 方法,在该方法中完成 RPC 请求:
147+
生成的代码将方法名、请求类型和返回类型(依赖类型推导)传递给 `ruapc_request` 方法,在该方法中完成 RPC 请求:
148148

149149
```rust
150150
impl Client {
@@ -216,7 +216,7 @@ pub struct MsgMeta {
216216
4. `MsgMeta` 序列化结果,长度为 `meta_len`
217217
5. 参数序列化结果,长度为 `total_len` - `meta_len` - 4,4 为大端 `u32` 的长度
218218

219-
按照上述规则实现序列化的逻辑,代码参考[ socket.rs 文件](https://github.com/SF-Zhou/ruapc/blob/b64248314de3eacfcbf2d6ab1f3ec5f7ad6a3edf/ruapc/src/socket.rs#L26-L105)[msg.rs 文件](https://github.com/SF-Zhou/ruapc/blob/b64248314de3eacfcbf2d6ab1f3ec5f7ad6a3edf/ruapc/src/msg.rs#L40-L69)
219+
按照上述规则实现序列化的逻辑,代码参考[ socket.rs](https://github.com/SF-Zhou/ruapc/blob/b64248314de3eacfcbf2d6ab1f3ec5f7ad6a3edf/ruapc/src/socket.rs#L26-L105) 文件和 [msg.rs](https://github.com/SF-Zhou/ruapc/blob/b64248314de3eacfcbf2d6ab1f3ec5f7ad6a3edf/ruapc/src/msg.rs#L40-L69) 文件
220220

221221
### 5. 连接管理
222222

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# 从零开始构建 Rust RPC 框架「二、渐入佳境」
2+
3+
### 1. 重构
4+
5+
上一篇博文中,阐述了如何打造一个最简化的 RPC 框架,但这离设想的星辰大海还很远。所以我对目前的代码进行了重构,使其基础结构能够支撑未来的开发需求。
6+
7+
回顾一下重点目标,基于这些目标需要完成如下的工作:
8+
9+
1. 传输协议支持 TCP、WebSocket 和 RDMA,那么:
10+
1. 要抽象出一层连接 Socket 和连接管理器 SocketPool,以支持不同的连接类型;
11+
2. 为了高性能地处理不同连接类型异步函数的动态派发,决定使用 `enum` 手动处理派发。
12+
2. 支持反向 RPC,server 可以调用 client 提供的方法,那么:
13+
1. 连接本身要支持全双工,server 端可以通过请求所在的连接发送对应的回包或者另一个新请求;
14+
2. client 端也可以注册方法,目前来看只能在 context 中管理注册方法的 router 了。
15+
3. 支持反射,server 能否向 client 暴露和描述自身所提供的服务和方法,那么:
16+
1. context 中可以获取到所有的方法及其类型定义,包含 router 的话可以解决该问题;
17+
2. 所有的请求参数和返回值均要求使用 [schemars](https://crates.io/crates/schemars) 修饰,暴露 JSON Schema。
18+
19+
基于上述的想法,进一步地明确 `Context` 的定义。客户端请求传入的 context 与服务端处理请求接收到的 context 的类型是完全一致的,我们既希望在获取到 context 的时候有能力处理请求、发起新请求,又希望在客户端可以支持注册方法,那么 context 实际上就需要包含了以下几部分:
20+
21+
首先是明确 `Context` 的定义。RuaPC 为了追求对称,客户端请求传入的 context 与服务端处理请求接收到的 context 的类型是完全一致的,并且希望在获取到 context 的时候有能力发起请求,那么 context 实际上就需要包含了以下几部分:
22+
23+
1. 连接管理器,client/server 发送请求时需要从 context 里获取连接;
24+
2. 方法路由,管理所有注册的服务和方法,并且有反射的能力;
25+
3. 请求自身相关信息,包括请求来源的 socket、附带的 meta 信息。
26+
27+
这些部分可以进一步地抽象为两部分:全局状态 `State` 和请求元信息 `ReqMeta`
28+
29+
### 2. 全双工通信
30+
31+
为了提高通信性能以及实现反向 RPC,需要在 TCP 上实现收发消息的全双工通信,做到发送与接收互不干扰。实现的策略也很简单,客户端在每一条请求中增加一个全局唯一的 msg id,服务端回包中附带请求的 msg id,客户端收到一致的 msg id 后唤醒对应的请求。

0 commit comments

Comments
 (0)