|
| 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 | + // 创建各个服务(使用 from_handler 方法) |
| 101 | + let hello_service = volo_gen::hello::HelloServiceServer::from_handler(HelloServiceImpl); |
| 102 | + let echo_service = volo_gen::echo::EchoServiceServer::from_handler(EchoServiceImpl); |
| 103 | + |
| 104 | + // 创建 Router |
| 105 | + let router = Router::new() |
| 106 | + .with_default_service(hello_service) // 设置默认服务 |
| 107 | + .add_service(echo_service); // 添加额外服务 |
| 108 | + |
| 109 | + // 启动 Server |
| 110 | + Server::with_router(router) |
| 111 | + .run(volo::net::Address::from(addr)) |
| 112 | + .await |
| 113 | + .unwrap(); |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +## 3. API 详解 |
| 118 | + |
| 119 | +### 3.1 Router |
| 120 | + |
| 121 | +`Router` 是多服务路由器,负责根据 ISN 将请求分发到对应的服务。 |
| 122 | + |
| 123 | +```rust |
| 124 | +use volo_thrift::server::Router; |
| 125 | + |
| 126 | +// 创建空路由器 |
| 127 | +let router = Router::new(); |
| 128 | + |
| 129 | +// 设置默认服务(处理无 ISN 或未知 ISN 的请求) |
| 130 | +let router = router.with_default_service(service_a); |
| 131 | + |
| 132 | +// 添加额外服务 |
| 133 | +let router = router.add_service(service_b); |
| 134 | + |
| 135 | +// 查询已注册服务数量 |
| 136 | +let count = router.service_count(); |
| 137 | + |
| 138 | +// 检查是否有默认服务 |
| 139 | +let has_default = router.has_default_service(); |
| 140 | +``` |
| 141 | + |
| 142 | +### 3.2 NamedService Trait |
| 143 | + |
| 144 | +每个服务需要实现 `NamedService` trait 以提供服务名称。代码生成器会自动为生成的 `*Server` 类型实现此 trait。 |
| 145 | + |
| 146 | +```rust |
| 147 | +pub trait NamedService { |
| 148 | + /// IDL 中定义的 service 名称 |
| 149 | + const NAME: &'static str; |
| 150 | +} |
| 151 | + |
| 152 | +// 自动生成的实现示例 |
| 153 | +impl<S> NamedService for HelloServiceServer<S> { |
| 154 | + const NAME: &'static str = "HelloService"; |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +### 3.3 Server::with_router |
| 159 | + |
| 160 | +使用 `Server::with_router` 创建支持多服务的 Server: |
| 161 | + |
| 162 | +```rust |
| 163 | +use volo_thrift::server::Server; |
| 164 | + |
| 165 | +let server = Server::with_router(router) |
| 166 | + .layer(SomeMiddleware) // 支持 layer |
| 167 | + .run(addr) |
| 168 | + .await?; |
| 169 | +``` |
| 170 | + |
| 171 | +## 4. 路由规则 |
| 172 | + |
| 173 | +Router 按以下优先级进行路由: |
| 174 | + |
| 175 | +1. **精确匹配**:如果请求携带 `isn` 且匹配某个已注册服务名称,路由到该服务 |
| 176 | +2. **默认回退**:如果请求无 `isn` 或 `isn` 不匹配任何服务,路由到默认服务 |
| 177 | +3. **错误处理**:如果无默认服务且无法匹配,返回 `ApplicationException(UNKNOWN_METHOD)` |
| 178 | + |
| 179 | +``` |
| 180 | +请求到达 |
| 181 | + │ |
| 182 | + ▼ |
| 183 | + 有 ISN? |
| 184 | + / \ |
| 185 | + 是 否 |
| 186 | + │ │ |
| 187 | + ▼ ▼ |
| 188 | +匹配服务? ──否──► 有默认服务? |
| 189 | + │ / \ |
| 190 | + 是 是 否 |
| 191 | + │ │ │ |
| 192 | + ▼ ▼ ▼ |
| 193 | +路由到匹配服务 路由到默认服务 返回错误 |
| 194 | +``` |
| 195 | + |
| 196 | +## 5. 生成代码变更 |
| 197 | + |
| 198 | +代码生成器为每个 `*Server` 类型自动生成以下实现: |
| 199 | + |
| 200 | +### 5.1 NamedService 实现 |
| 201 | + |
| 202 | +```rust |
| 203 | +impl<S> NamedService for HelloServiceServer<S> { |
| 204 | + const NAME: &'static str = "HelloService"; // IDL 中的 service 名称 |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +### 5.2 Service<ServerContext, Bytes> 实现 |
| 209 | + |
| 210 | +```rust |
| 211 | +impl<S> Service<ServerContext, Bytes> for HelloServiceServer<S> |
| 212 | +where |
| 213 | + S: HelloService + Send + Sync + 'static, |
| 214 | +{ |
| 215 | + type Response = Bytes; |
| 216 | + type Error = ServerError; |
| 217 | + |
| 218 | + async fn call(&self, cx: &mut ServerContext, payload: Bytes) |
| 219 | + -> Result<Bytes, ServerError> |
| 220 | + { |
| 221 | + // 从 context 重建消息标识(零拷贝) |
| 222 | + // 解码请求 |
| 223 | + // 调用业务逻辑 |
| 224 | + // 编码响应 |
| 225 | + } |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +### 5.3 服务创建方法 |
| 230 | + |
| 231 | +```rust |
| 232 | +#[derive(Clone)] |
| 233 | +pub struct HelloServiceServer<S> { |
| 234 | + inner: S, // 私有字段 |
| 235 | +} |
| 236 | + |
| 237 | +impl<S> HelloServiceServer<S> { |
| 238 | + /// 从 handler 创建服务实例,用于多服务路由 |
| 239 | + pub fn from_handler(handler: S) -> Self { |
| 240 | + Self { inner: handler } |
| 241 | + } |
| 242 | +} |
| 243 | + |
| 244 | +impl<S: HelloService + Send + Sync + 'static> HelloServiceServer<S> { |
| 245 | + /// 创建单服务 Server(传统用法) |
| 246 | + pub fn new(inner: S) -> Server<Self, ...> { |
| 247 | + Server::new(Self { inner }) |
| 248 | + } |
| 249 | +} |
| 250 | +``` |
| 251 | + |
| 252 | +## 6. 与 volo-grpc 对比 |
| 253 | + |
| 254 | +| 特性 | volo-thrift | volo-grpc | |
| 255 | +|------|-------------|-----------| |
| 256 | +| 路由标识 | TTHeader `isn` 字段 | gRPC path | |
| 257 | +| NamedService | 基于 IDL service 名称 | 基于 package.service 名称 | |
| 258 | +| Router API | 相同 | 相同 | |
| 259 | +| 默认服务 | 支持 | 支持 | |
| 260 | + |
| 261 | +## 7. 注意事项 |
| 262 | + |
| 263 | +1. **ISN 编码**:客户端需要在 TTHeader 的 string KV 中设置 `isn` 字段 |
| 264 | +2. **服务名称**:使用 IDL 中定义的原始 service 名称(非 Rust 名称) |
| 265 | +3. **Handler Clone**:Handler 实现需要实现 `Clone` trait |
| 266 | +4. **单服务兼容**:现有单服务代码无需修改,`Server::new` API 保持不变 |
| 267 | + |
| 268 | +## 8. 完整示例 |
| 269 | + |
| 270 | +参见 `examples/tests/thrift_multi_service.rs` 中的集成测试。 |
| 271 | + |
| 272 | +### 8.1 Server 示例 |
| 273 | + |
| 274 | +```rust |
| 275 | +use volo_thrift::server::{Router, Server}; |
| 276 | + |
| 277 | +#[derive(Clone)] |
| 278 | +struct HelloImpl; |
| 279 | +impl HelloService for HelloImpl { /* ... */ } |
| 280 | + |
| 281 | +#[derive(Clone)] |
| 282 | +struct EchoImpl; |
| 283 | +impl EchoService for EchoImpl { /* ... */ } |
| 284 | + |
| 285 | +#[volo::main] |
| 286 | +async fn main() { |
| 287 | + let addr = "127.0.0.1:8080".parse().unwrap(); |
| 288 | + |
| 289 | + let router = Router::new() |
| 290 | + .with_default_service(HelloServiceServer { inner: HelloImpl }) |
| 291 | + .add_service(EchoServiceServer { inner: EchoImpl }); |
| 292 | + |
| 293 | + Server::with_router(router) |
| 294 | + .run(volo::net::Address::from(addr)) |
| 295 | + .await |
| 296 | + .unwrap(); |
| 297 | +} |
| 298 | +``` |
| 299 | + |
| 300 | +### 8.2 测试路由逻辑 |
| 301 | + |
| 302 | +```rust |
| 303 | +use volo_thrift::server::Router; |
| 304 | +use volo_thrift::context::ThriftContext; |
| 305 | +use motore::service::Service; |
| 306 | + |
| 307 | +#[tokio::test] |
| 308 | +async fn test_routing() { |
| 309 | + let router = Router::new() |
| 310 | + .with_default_service(hello_service) |
| 311 | + .add_service(echo_service); |
| 312 | + |
| 313 | + // 测试 ISN 路由 |
| 314 | + let mut cx = ServerContext::default(); |
| 315 | + cx.set_idl_service_name("EchoService".into()); |
| 316 | + let resp = router.call(&mut cx, payload).await?; |
| 317 | + |
| 318 | + // 测试默认路由 |
| 319 | + let mut cx = ServerContext::default(); |
| 320 | + let resp = router.call(&mut cx, payload).await?; |
| 321 | +} |
| 322 | +``` |
| 323 | + |
| 324 | +## 9. 相关类型导出 |
| 325 | + |
| 326 | +以下类型从 `volo_thrift` 导出: |
| 327 | + |
| 328 | +```rust |
| 329 | +// 路由相关 |
| 330 | +pub use volo_thrift::server::{Router, NamedService}; |
| 331 | +pub use volo_thrift::server::Server; // with_router 方法 |
| 332 | + |
| 333 | +// ISN 相关(通过 ThriftContext trait) |
| 334 | +pub use volo_thrift::context::ThriftContext; // idl_service_name() / set_idl_service_name() |
| 335 | +pub use volo_thrift::codec::default::ttheader::HEADER_IDL_SERVICE_NAME; // ISN header key ("isn") |
| 336 | + |
| 337 | +// Bytes 类型 |
| 338 | +pub use volo_thrift::{Bytes, BytesMut}; |
| 339 | +``` |
0 commit comments