rurpc 工程讲解
src/rurpc 是 ruwebframe 框架的 RPC(Volo-gRPC)业务层,与它配套的通用框架层位于
src/rurpcframe。二者采用「框架 / 业务」分层设计:
- src/rurpcframe/ —— 可复用的 RPC 框架层,不依赖任何具体业务服务。提供
voloserver(服务端骨架与容器)、rpcplugin(生命周期插件)、rpcreg(etcd 注册)、
rpcclient(通用客户端)。 - src/rurpc/ —— 具体业务层,消费 rurpcframe 提供的通用能力,承载各 gRPC 业务服务
的实现(voloservice)、统一服务实现体(voloapp)、客户端业务门面(voloclients)
以及注册汇总入口(rpcrouter)。 - 代码生成依赖独立 crate volo-gen,后者在 volo-gen/build.rs 中依据
volo-gen/idl/*.proto 编译期生成桩代码;业务与框架代码统一通过 volo_gen::volo_gen::…
路径引用生成类型。
目录结构
src/
├── rurpcframe/ # ── 通用 RPC 框架层(与业务解耦)──
│ ├── voloserver/ # Volo-gRPC 服务端(骨架 + 容器 + 路由)
│ │ ├── mod.rs # 模块声明 + register_rpc! 宏 + re-export
│ │ ├── rpc_server.rs # RpcServer:start / serve / build_server
│ │ ├── rpc_server_init.rs # RpcServer 的 DI Bean 注册
│ │ ├── rpc_container.rs # RpcContainer:注册函数表 + serve
│ │ ├── rpc_container_init.rs
│ │ ├── volo_routes.rs # VoloRoutes / RpcRouterLayer / register_service
│ │ ├── rpc_server_test.rs
│ │ └── rpc_container_test.rs
│ ├── rpcplugin/ # RPC 生命周期插件(反向注册机制)
│ │ ├── rpc_plugin.rs # RpcPlugin:实现 Plugin trait + register_client
│ │ ├── rpc_plugin_init.rs # #[ctor] 自动注册单例 Bean
│ │ └── rpc_plugin_test.rs
│ ├── rpcreg/ # RPC 服务注册(etcd,参考 webreg)
│ │ ├── rpc_etcd_reg.rs # RpcEtcdReg / RpcRegValue
│ │ ├── rpcreg_plugin.rs # RpcregPlugin:start 注册、stop 注销
│ │ ├── rpcreg_plugin_init.rs
│ │ └── rpcreg_plugin_test.rs
│ └── rpcclient/ # 通用 Volo-gRPC 客户端
│ └── rpc_client.rs # RpcClient<C>:懒加载 + etcd 服务发现
│
└── rurpc/ # ── RPC 业务层(消费框架能力)──
├── mod.rs # 模块声明
├── main.rs # 独立演示 bin:装配各插件(含 RPC)
├── rpcrouter/ # 注册汇总入口(插件反向注册的发起方)
│ ├── mod.rs
│ └── register_client.rs # register_client():把业务注册函数塞回插件
├── rpcservice/ # 业务服务实现
│ ├── mod.rs
│ ├── register_rpc.rs # 依次触发全部服务自注册
│ ├── voloapp/ # 统一服务实现体(各 *Service trait 的载体)
│ │ ├── volo_app.rs # VoloApp:实现 BaseEntitySingle
│ │ ├── volo_app_init.rs
│ │ └── volo_app_test.rs
│ └── voloservice/ # 每个服务一个文件
│ ├── hello_service.rs # 问候服务:codec + impl HelloService + 自注册
│ ├── item_service.rs # 商品服务:codec + impl ItemService + 自注册
│ ├── mail_service.rs # 邮件服务:codec + impl MailService + 自注册
│ ├── user_service.rs # 用户积分服务:codec + impl UserService + 自注册
│ ├── center_service.rs # 服务中心:codec + impl CenterService + 自注册
│ ├── file_service.rs # 文件服务:codec + impl FileService + 自注册
│ └── login_service.rs # 登录服务:codec + impl LoginService + 自注册
└── voloclients/ # 客户端业务门面(面向调用方)
├── mod.rs
├── rpc_clients.rs # RpcClients:为每个服务持有一个 RpcClient
├── rpc_clients_init.rs # #[ctor] DI Bean 注册
└── rpc_clients_test.rs
一、代码生成(地基)
接口定义在 volo-gen/idl/*.proto,目前共 6 个参与编译的服务:
| volo_hello.proto | hello | hello.HelloService、hello.ItemService |
| volo_mail.proto | mail.MailService | |
| volo_user.proto | user | user.UserService |
| volo_center.proto | center | center.CenterService |
| volo_file.proto | file | file.FileService |
| volo_login.proto | login | login.LoginService |
volo-gen/build.rs 编译上面 6 个 proto 生成 volo_gen.rs;volo_example.proto
是模板样例,未参与编译。
为每个服务生成:
- *Service(trait,由业务层实现)、*ServiceServer(服务端构建器)
- *RequestRecv / *ResponseSend(编解码类型)
- *ServiceClientBuilder、*ServiceClient(客户端)
二、通用框架层 rurpcframe/voloserver
2.1 启动骨架 RpcServer
RpcServer 是单例 Bean(rpc_server_init.rs),负责启动与注入,不点名任何具体服务:
启动:RpcServer::start() 开独立线程 + 自建 tokio 运行时,避免占用主线程。
绑定:serve() 读取 RpcConfig(enable / serverName / serverPort),enable=false
时直接跳过;否则绑定 0.0.0.0:port,并用 local_addr() 打印实际端口。
注入路由:build_server() 把路由容器整体作为内层 Layer 注入 Server:
pub fn build_server<IL, OL>(
&self,
server: Server<IL, OL>,
) -> Server<Stack<RpcRouterLayer, IL>, OL> {
server.layer(RpcRouterLayer(VoloRoutes::global().router()))
}
2.2 路由容器 VoloRoutes(volo_routes.rs)
对齐 web 的 RouteContainer:各服务自行登记 codec,容器统一持有,启动时一次性注入。
- 内部 RwLock<Router>;Router 是 volo 公开类型,内部用 BoxCloneService 装箱,
因此类型各异的 codec 能收敛进同一容器。 - new() 建空容器;global() 提供惰性单例(Once + OnceLock);register(codec) 写入;
router() 取出供注入。 - register() 内部用 std::mem::take 取出旧路由,add_service(codec) 后写回。
- RpcRouterLayer 把取出的 Router 直接作为 Server 的内层 Service
(Layer::layer 忽略 inner)。 - 同文件导出自由函数 register_service(codec),作为各服务的自注册入口。
// volo_routes.rs
pub struct VoloRoutes {
router: RwLock<Router>,
}
impl VoloRoutes {
pub fn new() -> Self { /* 空路由容器 */ }
pub fn global() -> &'static VoloRoutes { /* Once + OnceLock */ }
pub fn register<C>(&self, codec: C) { /* take + add_service + put back */ }
pub fn router(&self) -> Router { /* read().clone() */ }
}
pub fn register_service<C>(codec: C) { VoloRoutes::global().register(codec); }
2.3 注册函数表 RpcContainer(rpc_container.rs)
单例 Bean,持有注册函数表 RwLock<Vec<fn()>>,并提供运行入口:
- register(registrar):把一个 fn() 型注册函数压入表(通过自由函数
rpc_server::register_rpc(fn()) 调用)。 - register_all_services():Once 保护下遍历执行表内注册函数(幂等)。
- serve(incoming, signal):先 register_all_services(),再
RpcServer::build_server(Server::new()),最后 run_with_shutdown。
2.4 业务实现挂载与 register_rpc! 宏
框架层不关心业务实现写在哪里,只约定挂载机制:
-
各业务服务文件里的 register_rpc() 会调用 voloserver::register_service(codec),
直接写入 VoloRoutes —— 这是服务自注册到路由容器的主干路径。 -
macro register_rpc!(#[macro_export],定义在 voloserver/mod.rs)统一生成
codec 构建函数与自注册函数:// voloserver/mod.rs
macro_rules! register_rpc {
($name:ident, $srv:ident, $recv:ty, $send:ty, $doc:expr) => {
fn $name() -> impl volo::Service<ServerContext, Request<BoxBody>,
Response = Response<BoxBody>, Error = Status>
+ NamedService + Clone + Send + Sync + 'static {
volo_grpc::server::ServiceBuilder::new($srv::new(
crate::rurpc::rpcservice::voloapp::VoloApp::new(),
))
.build::<$recv, $send>()
}
pub fn register_rpc() {
crate::rurpcframe::voloserver::register_service($name());
}
};
}
两条注册路径(重要)
- 路径 A(当前生效):RpcPlugin::start.register() 阶段遍历插件内登记的业务
注册函数,调用各 xxx_service::register_rpc() →
voloserver::register_service(codec) → 直接写入 VoloRoutes,
不经过 RpcContainer 的函数表。 - 路径 B(预留):voloserver::rpc_server::register_rpc(fn()) 把注册函数压入
RpcContainer.registrars,由 serve() 中的 register_all_services() 统一触发。
当前只接入路径 A,故 RpcContainer.registrars 实际为空,register_all_services() 是
无副作用的空遍历;路由仍能正常注入,因为 build_server 读取的是 VoloRoutes。
三、通用框架层 rurpcframe/rpcplugin(生命周期)
RpcPlugin 实现框架 Plugin trait,采用「反向注册」设计,保持插件通用、与业务解耦:
// rpc_plugin.rs
pub struct RpcPlugin {
registrars: RwLock<Vec<fn()>>, // 业务方塞进来的服务注册函数
}
impl RpcPlugin {
/// 客户端反向注册:把一个服务注册函数嵌入插件。
pub fn register_client(&self, registrar: fn()) {
self.registrars.write().unwrap().push(registrar);
}
}
impl Plugin for RpcPlugin {
fn name(&self) -> &'static str { "rpc_plugin" }
fn start(&self) -> Result<(), AppError> {
self.init()?;
self.register()?;
match RpcServer::new().start() { /* 拉起 gRPC 服务 */ }
Ok(())
}
fn start_with_thread(&self) -> bool { true } // 线程内启动
fn register(&self) -> Result<(), AppError> {
for registrar in self.registrars.read().unwrap().iter() {
registrar(); // 统一遍历执行业务注册函数
}
Ok(())
}
}
rpc_plugin.rs 顶部还导出了一个同名自由函数,供业务层发起反向注册:
pub fn register_client(registrar: fn()) {
let rpc_plugin = get_bean_unwrap!(RpcPlugin);
rpc_plugin.register_client(registrar);
}
由 #[ctor](rpc_plugin_init.rs)自动注册为单例 Bean。
四、通用框架层 rurpcframe/rpcreg(服务发现)
RpcregPlugin 把当前 RPC 服务注册到 etcd,复用 webreg 的 EtcdReg 底层:
- key:/ruwebframe/rpc/{serverName}/{host}:{port}(附带实例维度,同服务可多实例)。
- value:RpcRegValue。host 取配置 rpc.ip(非空优先),否则回退本机内网 IP;
url=grpc://host:port、serviceType="rpc"(常量 SERVICE_TYPE_RPC)等。 - start_register()(经 Plugin::start)异步注册并持续续期(TTL 10s),
stop_register()(经 Plugin::stop)注销。 - 未配置 etcd 地址或 rpc.enable=false 时,from_config() 返回 None,跳过注册。
五、通用框架层 rurpcframe/rpcclient(客户端)
RpcClient<C> 是通用客户端,C 为某服务生成的 client 类型(*ServiceClient),
一套逻辑服务所有服务:
#[derive(Clone)]
pub struct RpcClient<C> {
service_name: &'static str, // gRPC 逻辑服务名(如 hello.ItemService)
server_name: String, // 注册中心服务名;为空时地址解析从配置读取
build: fn(&'static str, SocketAddr) -> C, // client 构建函数
cell: OnceLock<C>, // 懒加载缓存
}
impl<C: Clone> RpcClient<C> {
pub fn new(service_name: &'static str,
server_name: impl Into<String>,
build: fn(&'static str, SocketAddr) -> C) -> Self;
pub async fn client(&self) -> Result<C, String>; // 取缓存或解析地址构建
}
- 地址解析 resolve_target(server_name) 收敛为一个自由函数(rpc_client.rs):
- 读全局配置 RpcDto,未启用(enable=false)或端口非法返回 None;
- server_name 为空时回退到配置 serverName;
- 优先通过 etcd 服务发现(query_etcd_addr,按服务名查已注册实例),命中则用其 host:port;
- 未命中回退到配置 ip:port;地址仍非法则回退 127.0.0.1 并打日志。
- client() 返回 Result<C, String>,未配置可用地址时返回错误字符串。
- OnceLock 缓存,每个服务只建一次。
六、业务层 rurpc/rpcservice
6.1 统一服务实现体 VoloApp(voloapp/volo_app.rs)
宏 register_rpc! 用 VoloApp::new() 作为每个服务端的业务实现载体:
// rpcservice/voloapp/volo_app.rs
pub struct VoloApp;
impl BaseEntitySingle for VoloApp {} // 以单例 Bean 存在,可注入业务
impl VoloApp { pub fn new() -> Self { Self {} } }
每个 *Service trait 都在 impl … for VoloApp 中实现,保证所有服务共享同一个
实现体,便于在 trait 方法内 get_bean! 注入其它业务 Bean。
6.2 业务实现 voloservice(每服务一个文件)
每个服务一个文件,codec 构建、业务实现、自注册三件事都在同一文件里,对齐 web
「每个 controller 一个文件」的分离形态。典型结构:
// rpcservice/voloservice/hello_service.rs
use crate::rurpcframe::voloserver::RpcServer; // 仅示意,实际实现挂在 VoloApp 上
use crate::rurpc::rpcservice::voloapp::VoloApp;
use crate::register_rpc;
register_rpc!(
codec,
HelloServiceServer,
HelloServiceRequestRecv,
HelloServiceResponseSend,
"构建问候服务 `HelloService` 的编解码器。"
);
impl HelloService for VoloApp {
async fn say_hello(&self, req: Request<HelloRequest>)
-> Result<Response<HelloResponse>, Status> { /* … */ }
}
业务代理示例:impl MailServiceRpc for VoloApp 把请求转成 SendMailDto,再
get_bean_unwrap!(MailService) 代理给业务 Bean MailService::send。
6.3 汇总入口 rpcservice/register_rpc.rs
pub fn register_rpc() {
item_service::register_rpc();
hello_service::register_rpc();
mail_service::register_rpc();
user_service::register_rpc();
center_service::register_rpc();
file_service::register_rpc();
login_service::register_rpc();
}
七、业务层 rurpc/rpcrouter 与装配 main.rs
7.1 反向注册入口 rpcrouter/register_client.rs
业务层通过该入口把「服务注册函数」反向塞回框架插件,实现插件通用化:
// rpcrouter/register_client.rs
pub fn register_client() {
// 反向注册自己的任意服务注册函数,可多次调用
rpc_plugin::register_client(rpcservice::register_rpc);
}
7.2 装配入口 rurpc/main.rs
main.rs 通过 ctx.add_register(…) 把插件加入上下文并同时触发一次登记函数
(add_register 会立即执行传入的 registrar),随后 ctx.start() 统一启动所有插件:
ctx.add(get_bean_unwrap!(RpcregPlugin)); // RPC 服务注册(etcd)
ctx.add_register(get_bean_unwrap!(RpcPlugin), rpcrouter::register_client); // RPC 服务
7.3 两端调用链
服务端注册/启动(RpcPlugin::start,线程内):
ctx.add_register(RpcPlugin, rpcrouter::register_client) // add 时即执行
└─ register_client()
└─ rpc_plugin::register_client(rpcservice::register_rpc)
└─ 把 rpcservice::register_rpc 压入 RpcPlugin.registrars
ctx.start → RpcPlugin::start(线程内)
├─ init()
├─ register()
│ └─ 遍历 registrars,执行 rpcservice::register_rpc()
│ ├─ item_service::register_rpc() → register_service(codec)
│ ├─ hello_service::register_rpc() → register_service(codec)
│ ├─ …(mail/user/center/file/login)
│ └─ 全部写入 VoloRoutes(Once 幂等由各注册函数自身保证)
└─ RpcServer::start → serve(独立线程 + 自建 tokio)
├─ RpcContainer::register_all_services()(当前表为空)
├─ RpcServer::build_server → layer(RpcRouterLayer(VoloRoutes::global().router()))
└─ run_with_shutdown
└─ Router 分发 → 各 *ServiceServer → VoloApp 的 trait 实现 → 业务 Bean
客户端调用(VoloApp / 控制器等注入 RpcClients):
RpcClients { get_item / say_hello / send_mail / list_user / service_list / file_upload / login_rpc … }
→ RpcClient<C>::client()(OnceLock 缓存 + resolve_target:etcd 发现 → 配置 ip:port → 127.0.0.1)
→ volo_gen client → 对端 RpcServer(VoloApp 实现)
八、新增一个 gRPC 服务
add_service("./idl/volo_xxx.proto"),按其定义生成 client(cargo build 触发)。
再写 impl XxxService for VoloApp。
增加一个 RpcClient<XxxServiceClient> 字段并暴露业务方法。
无需改动 rpc_server.rs / voloserver / rpcplugin——服务端骨架、路由容器与注册机制
完全与服务数量解耦,新增服务只在业务层 rurpc 内完成。
命名提示:voloserver/mod.rs 的宏名为 register_rpc!,它生成的函数也叫
register_rpc();voloserver::rpc_server 另有自由函数 register_rpc(fn());
rpcservice::register_rpc 有同名函数;rpc_plugin 与 rpcrouter::register_client
也有同名 register_client()。它们处于不同模块作用域,调用时需写全路径以免混淆。
注册机制对比
| 路由容器 | RouteContainer | — | VoloRoutes |
| 登记项 | 路由配置 | 服务 codec | — |
| 自注册 | 各 controller reg_route | 各 xxx_service::register_rpc() | register_service |
| 汇总入口 | apply_routes(conf) | rpcservice::register_rpc() | — |
| 反向注入 | — | rpcrouter::register_client() | RpcPlugin::register_client |
| 生命周期 | — | — | RpcPlugin / RpcregPlugin |
| 注入 | 装配到 App | — | layer(RpcRouterLayer(router)) |
| 单例 | OnceLock | — | Once + OnceLock / DI Bean |
一图流目录职责小结
| voloserver | 服务端骨架 + 容器 + 路由 | RpcServer / RpcContainer / VoloRoutes |
| rpcplugin | 生命周期驱动(反向注册) | RpcPlugin |
| rpcreg | 注册中心 | RpcregPlugin / RpcEtcdReg / RpcRegValue |
| rpcclient | 通用客户端 | RpcClient<C> |
| rpcservice/voloservice | 业务实现(每服务一文件) | register_rpc! 生成的服务实现 |
| rpcservice/voloapp | 统一服务实现体 | VoloApp |
| voloclients | 调用方门面 | RpcClients(包装多个 RpcClient<C>) |
| rpcrouter | 反向注册入口 | register_client() |
| main.rs | 演示装配入口 | 装配 RpcPlugin / RpcregPlugin |
网硕互联帮助中心





评论前必须登录!
注册