Skip to content

Commit 9121484

Browse files
committed
feat(volo-thrift): add multi service support
1 parent a7d2463 commit 9121484

13 files changed

Lines changed: 1356 additions & 25 deletions

File tree

.github/workflows/security.yaml

Lines changed: 0 additions & 17 deletions
This file was deleted.

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/thrift-multi-service.md

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
# Volo-Thrift Multi Service 使用指南
2+
3+
## 1. 功能概述
4+
5+
Multi Service 功能允许一个 volo-thrift Server 同时处理多个 Thrift Service,通过 TTHeader 中的 `isn` (IDL Service Name) 字段进行路由。
6+
7+
### 主要特性
8+
9+
- **多服务支持**:单个 Server 可注册多个 Thrift Service
10+
- **ISN 路由**:根据请求头中的 `isn` 字段自动路由到对应服务
11+
- **默认服务**:支持设置默认服务处理无 ISN 或未知 ISN 的请求
12+
- **零拷贝**:基于 `Bytes` 的请求/响应传递,避免重复序列化
13+
- **向后兼容**:单服务模式 API 保持不变
14+
15+
## 2. 快速开始
16+
17+
### 2.1 定义多个 Thrift Service
18+
19+
```thrift
20+
// hello.thrift
21+
namespace rs hello
22+
23+
struct HelloRequest {
24+
1: required string name,
25+
}
26+
27+
struct HelloResponse {
28+
1: required string message,
29+
}
30+
31+
service HelloService {
32+
HelloResponse hello(1: HelloRequest req),
33+
}
34+
```
35+
36+
```thrift
37+
// echo.thrift
38+
namespace rs echo
39+
40+
struct EchoRequest {
41+
1: required string message,
42+
}
43+
44+
struct EchoResponse {
45+
1: required string message,
46+
}
47+
48+
service EchoService {
49+
EchoResponse echo(1: EchoRequest req),
50+
}
51+
```
52+
53+
### 2.2 实现 Service Handler
54+
55+
```rust
56+
use volo_gen::hello::{HelloService, HelloRequest, HelloResponse};
57+
use volo_gen::echo::{EchoService, EchoRequest, EchoResponse};
58+
59+
// HelloService 实现
60+
#[derive(Clone)]
61+
struct HelloServiceImpl;
62+
63+
impl HelloService for HelloServiceImpl {
64+
async fn hello(
65+
&self,
66+
req: HelloRequest,
67+
) -> Result<HelloResponse, volo_thrift::ServerError> {
68+
Ok(HelloResponse {
69+
message: format!("Hello, {}!", req.name).into(),
70+
})
71+
}
72+
}
73+
74+
// EchoService 实现
75+
#[derive(Clone)]
76+
struct EchoServiceImpl;
77+
78+
impl EchoService for EchoServiceImpl {
79+
async fn echo(
80+
&self,
81+
req: EchoRequest,
82+
) -> Result<EchoResponse, volo_thrift::ServerError> {
83+
Ok(EchoResponse {
84+
message: req.message,
85+
})
86+
}
87+
}
88+
```
89+
90+
### 2.3 创建 Multi Service Server
91+
92+
```rust
93+
use volo_thrift::server::{Router, Server};
94+
use std::net::SocketAddr;
95+
96+
#[volo::main]
97+
async fn main() {
98+
let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
99+
100+
// 创建各个服务
101+
let hello_service = volo_gen::hello::HelloServiceServer {
102+
inner: HelloServiceImpl,
103+
};
104+
let echo_service = volo_gen::echo::EchoServiceServer {
105+
inner: EchoServiceImpl,
106+
};
107+
108+
// 创建 Router
109+
let router = Router::new()
110+
.with_default_service(hello_service) // 设置默认服务
111+
.add_service(echo_service); // 添加额外服务
112+
113+
// 启动 Server
114+
Server::with_router(router)
115+
.run(volo::net::Address::from(addr))
116+
.await
117+
.unwrap();
118+
}
119+
```
120+
121+
### 2.4 客户端指定 ISN
122+
123+
客户端可通过 metainfo 设置 `isn` 字段来指定目标服务:
124+
125+
```rust
126+
use std::sync::Arc;
127+
use metainfo::MetaInfo;
128+
129+
// 调用 EchoService
130+
metainfo::METAINFO.scope(std::cell::RefCell::new(MetaInfo::default()), async {
131+
metainfo::METAINFO.with(|mi| {
132+
mi.borrow_mut().set_persistent(
133+
volo_thrift::codec::default::ttheader::HEADER_IDL_SERVICE_NAME.into(),
134+
"EchoService".into(),
135+
);
136+
});
137+
138+
let resp = echo_client.echo(req).await?;
139+
// ...
140+
}).await;
141+
```
142+
143+
## 3. API 详解
144+
145+
### 3.1 Router
146+
147+
`Router` 是多服务路由器,负责根据 ISN 将请求分发到对应的服务。
148+
149+
```rust
150+
use volo_thrift::server::Router;
151+
152+
// 创建空路由器
153+
let router = Router::new();
154+
155+
// 设置默认服务(处理无 ISN 或未知 ISN 的请求)
156+
let router = router.with_default_service(service_a);
157+
158+
// 添加额外服务
159+
let router = router.add_service(service_b);
160+
161+
// 查询已注册服务数量
162+
let count = router.service_count();
163+
164+
// 检查是否有默认服务
165+
let has_default = router.has_default_service();
166+
```
167+
168+
### 3.2 NamedService Trait
169+
170+
每个服务需要实现 `NamedService` trait 以提供服务名称。代码生成器会自动为生成的 `*Server` 类型实现此 trait。
171+
172+
```rust
173+
pub trait NamedService {
174+
/// IDL 中定义的 service 名称
175+
const NAME: &'static str;
176+
}
177+
178+
// 自动生成的实现示例
179+
impl<S> NamedService for HelloServiceServer<S> {
180+
const NAME: &'static str = "HelloService";
181+
}
182+
```
183+
184+
### 3.3 Server::with_router
185+
186+
使用 `Server::with_router` 创建支持多服务的 Server:
187+
188+
```rust
189+
use volo_thrift::server::Server;
190+
191+
let server = Server::with_router(router)
192+
.layer(SomeMiddleware) // 支持 layer
193+
.run(addr)
194+
.await?;
195+
```
196+
197+
## 4. 路由规则
198+
199+
Router 按以下优先级进行路由:
200+
201+
1. **精确匹配**:如果请求携带 `isn` 且匹配某个已注册服务名称,路由到该服务
202+
2. **默认回退**:如果请求无 `isn``isn` 不匹配任何服务,路由到默认服务
203+
3. **错误处理**:如果无默认服务且无法匹配,返回 `ApplicationException(UNKNOWN_METHOD)`
204+
205+
```
206+
请求到达
207+
208+
209+
有 ISN?
210+
/ \
211+
是 否
212+
│ │
213+
▼ ▼
214+
匹配服务? ──否──► 有默认服务?
215+
│ / \
216+
是 是 否
217+
│ │ │
218+
▼ ▼ ▼
219+
路由到匹配服务 路由到默认服务 返回错误
220+
```
221+
222+
## 5. 生成代码变更
223+
224+
代码生成器为每个 `*Server` 类型自动生成以下实现:
225+
226+
### 5.1 NamedService 实现
227+
228+
```rust
229+
impl<S> NamedService for HelloServiceServer<S> {
230+
const NAME: &'static str = "HelloService"; // IDL 中的 service 名称
231+
}
232+
```
233+
234+
### 5.2 Service<ServerContext, Bytes> 实现
235+
236+
```rust
237+
impl<S> Service<ServerContext, Bytes> for HelloServiceServer<S>
238+
where
239+
S: HelloService + Send + Sync + 'static,
240+
{
241+
type Response = Bytes;
242+
type Error = ServerError;
243+
244+
async fn call(&self, cx: &mut ServerContext, payload: Bytes)
245+
-> Result<Bytes, ServerError>
246+
{
247+
// 从 context 重建消息标识(零拷贝)
248+
// 解码请求
249+
// 调用业务逻辑
250+
// 编码响应
251+
}
252+
}
253+
```
254+
255+
### 5.3 Clone 实现
256+
257+
```rust
258+
#[derive(Clone)]
259+
pub struct HelloServiceServer<S> {
260+
pub inner: S,
261+
}
262+
```
263+
264+
## 6. 与 volo-grpc 对比
265+
266+
| 特性 | volo-thrift | volo-grpc |
267+
|------|-------------|-----------|
268+
| 路由标识 | TTHeader `isn` 字段 | gRPC path |
269+
| NamedService | 基于 IDL service 名称 | 基于 package.service 名称 |
270+
| Router API | 相同 | 相同 |
271+
| 默认服务 | 支持 | 支持 |
272+
273+
## 7. 注意事项
274+
275+
1. **ISN 编码**:客户端需要在 TTHeader 的 string KV 中设置 `isn` 字段
276+
2. **服务名称**:使用 IDL 中定义的原始 service 名称(非 Rust 名称)
277+
3. **Handler Clone**:Handler 实现需要实现 `Clone` trait
278+
4. **单服务兼容**:现有单服务代码无需修改,`Server::new` API 保持不变
279+
280+
## 8. 完整示例
281+
282+
参见 `examples/tests/thrift_multi_service.rs` 中的集成测试。
283+
284+
### 8.1 Server 示例
285+
286+
```rust
287+
use volo_thrift::server::{Router, Server};
288+
289+
#[derive(Clone)]
290+
struct HelloImpl;
291+
impl HelloService for HelloImpl { /* ... */ }
292+
293+
#[derive(Clone)]
294+
struct EchoImpl;
295+
impl EchoService for EchoImpl { /* ... */ }
296+
297+
#[volo::main]
298+
async fn main() {
299+
let addr = "127.0.0.1:8080".parse().unwrap();
300+
301+
let router = Router::new()
302+
.with_default_service(HelloServiceServer { inner: HelloImpl })
303+
.add_service(EchoServiceServer { inner: EchoImpl });
304+
305+
Server::with_router(router)
306+
.run(volo::net::Address::from(addr))
307+
.await
308+
.unwrap();
309+
}
310+
```
311+
312+
### 8.2 测试路由逻辑
313+
314+
```rust
315+
use volo_thrift::server::Router;
316+
use volo_thrift::context::ThriftContext;
317+
use motore::service::Service;
318+
319+
#[tokio::test]
320+
async fn test_routing() {
321+
let router = Router::new()
322+
.with_default_service(hello_service)
323+
.add_service(echo_service);
324+
325+
// 测试 ISN 路由
326+
let mut cx = ServerContext::default();
327+
cx.set_idl_service_name("EchoService".into());
328+
let resp = router.call(&mut cx, payload).await?;
329+
330+
// 测试默认路由
331+
let mut cx = ServerContext::default();
332+
let resp = router.call(&mut cx, payload).await?;
333+
}
334+
```
335+
336+
## 9. 相关类型导出
337+
338+
以下类型从 `volo_thrift` 导出:
339+
340+
```rust
341+
// 路由相关
342+
pub use volo_thrift::server::{Router, NamedService};
343+
pub use volo_thrift::server::Server; // with_router 方法
344+
345+
// ISN 相关(通过 ThriftContext trait)
346+
pub use volo_thrift::context::ThriftContext; // idl_service_name() / set_idl_service_name()
347+
pub use volo_thrift::codec::default::ttheader::HEADER_IDL_SERVICE_NAME; // ISN header key ("isn")
348+
349+
// Bytes 类型
350+
pub use volo_thrift::{Bytes, BytesMut};
351+
```

0 commit comments

Comments
 (0)