ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

rurpc

rurpc

rurpc 工程讲解

src/rurpc是 ruwebframe 框架的RPC 模块,基于字节跳动开源的 Volo-gRPC 实现远程过程调用。它同时内置了服务端、客户端、服务发现注册三块能力。

代码生成依赖独立 cratevolo-gen,后者在build.rs中依据idl/*.proto编译期生成桩代码,rurpc通过volo_gen::volo_gen::...路径引用。

目录结构

src/rurpc/ ├── mod.rs # 模块声明入口 ├── main.rs # 独立演示 bin 入口(装配并启动 RPC 及其它插件) ├── rpcplugin/ # RPC 生命周期插件(真正拉起 gRPC 服务) │ ├── rpc_plugin.rs # RpcPlugin:实现 Plugin trait,start 时注册并启动服务 │ ├── 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 ├── rpcrouter/ # 注册汇总入口(插件层调用的唯一入口) │ ├── mod.rs │ └── register_rpc.rs # register_rpc():Once 幂等,转调 rpcservice::register_rpc() ├── rpcservice/ # 业务服务实现 │ ├── mod.rs # 子模块声明 │ ├── register_rpc.rs # 依次触发三个服务自注册 │ └── voloservice/ # 每个服务一个文件 │ ├── item_service.rs # 商品服务:codec + impl ItemService + 自注册 │ ├── hello_service.rs # 问候服务:codec + impl HelloService + 自注册 │ └── mail_service.rs # 邮件服务:codec + impl MailService + 自注册 ├── voloclient/ # Volo-gRPC 客户端 │ ├── rpc_client.rs # RpcClient<C> 通用客户端 + RpcClients 业务门面 │ ├── rpc_client_init.rs │ └── rpc_client_test.rs └── voloserver/ # Volo-gRPC 服务端 ├── mod.rs # register_rpc! 宏 + 模块声明 + re-export ├── rpc_server.rs # RpcServer:启动/绑定/注入路由 ├── rpc_container.rs # RpcContainer:注册函数表 + serve ├── volo_routes.rs # VoloRoutes 路由容器 + RpcRouterLayer + register_service ├── rpc_server_init.rs # RpcServer 的 DI Bean 注册 ├── rpc_container_init.rs # RpcContainer 的 DI Bean 注册 └── rpc_server_test.rs

一、代码生成(地基)

接口定义在idl/*.proto:

proto 文件package逻辑服务名
volo_example.protovolo.examplevolo.example.ItemService
volo_hello.protohellohello.HelloService
volo_mail.protomailmail.MailService

volo-gen/build.rs编译这些 proto 生成volo_gen.rs,为每个服务生成:

  • *Service(trait,服务端需实现)、*ServiceServer
  • *RequestRecv/*ResponseSend
  • *ServiceClientBuilder、*ServiceClient

二、服务端 voloserver

2.1 启动骨架RpcServer

RpcServer是单例 Bean,负责启动与注入,不点名任何具体服务:

  1. 启动:RpcServer::start()开独立线程 + 自建 tokio 运行时,避免占用主线程。

  2. 绑定:serve()读取RpcConfig(enable / serverName / serverPort),绑定0.0.0.0:port,并用local_addr()打印实际端口。

  3. 注入路由:build_server()把路由容器整体作为内层Layer注入Server:

    pubfnbuild_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 能收敛进同一容器。
  • global()提供惰性单例(Once + OnceLock);register(codec)写入;router()取出供注入。
  • RpcRouterLayer把取出的Router直接作为Server的内层Service(Layer::layer忽略 inner)。
  • 同文件导出自由函数register_service(codec),作为各服务的自注册入口。
pubstructVoloRoutes{router:RwLock<Router>,}implVoloRoutes{pubfnglobal()->&'staticVoloRoutes{/* Once + OnceLock */}pubfnregister<C>(&self,codec:C){/* take + add_service + put back */}pubfnrouter(&self)->Router{/* read().clone() */}}pubfnregister_service<C>(codec:C){VoloRoutes::global().register(codec);}

2.3 服务容器RpcContainer(rpc_container.rs)

单例 Bean,持有注册函数表RwLock<Vec<fn()>>,并提供运行入口:

  • register(registrar):把一个fn()型注册函数压入表。
  • register_all_services():Once保护下遍历执行表内注册函数(幂等,避免同路径重复挂载导致 voloRouterpanic)。
  • serve(incoming, signal):先register_all_services(),再RpcServer::build_server(Server::new()),最后run_with_shutdown。

两条注册路径说明(重要)

  • 路径 A(当前默认生效):RpcPlugin::register→rpcrouter::register_rpc()→
    rpcservice::register_rpc()→ 各xxx_service::register_rpc()→
    voloserver::register_service(codec)→ 直接写入VoloRoutes。
    不经过RpcContainer的函数表。
  • 路径 B(预留):自由函数rpc_server::register_rpc(fn())把注册函数压入
    RpcContainer,再由register_all_services()统一触发。

当前只有路径 A 被接入,因此RpcContainer.registrars实际为空,
register_all_services()是无副作用的空遍历;路由仍能正常注入,因为
build_server读取的是VoloRoutes。两条路径都受Once幂等保护。

2.4 业务实现rpcservice/voloservice

每个服务一个文件,codec 构建、业务实现、自注册三件事都在同一文件里。
统一由宏register_rpc!(#[macro_export],定义在voloserver/mod.rs)生成 codec 函数与自注册函数:

// voloserver/mod.rsmacro_rules!register_rpc{($name:ident,$srv:ident,$recv:ty,$send:ty,$doc:expr)=>{#[doc = $doc]fn$name()->implService<...>+NamedService+Clone+Send+Sync+'static{ServiceBuilder::new($srv::new(RpcServer::new())).build::<$recv,$send>()}/// 登记本服务到全局路由容器。pubfnregister_rpc(){crate::rurpc::voloserver::register_service($name());}};}

服务文件里只需一行宏调用 + 一段impl:

// rpcservice/voloservice/hello_service.rsusecrate::{register_rpc,rulog};register_rpc!(codec,HelloServiceServer,HelloServiceRequestRecv,HelloServiceResponseSend,"构建问候服务 `HelloService` 的编解码器。");implHelloServiceforRpcServer{/* say_hello ... */}

业务实现示例:impl MailServiceRpc for RpcServer把请求转成SendMailDto,再代理给业务 BeanMailService::send。

2.5 汇总入口rpcrouter

rpcrouter::register_rpc::register_rpc()是插件层调用的唯一注册入口,
Once保证幂等,内部转调rpcservice::register_rpc():

// rpcrouter/register_rpc.rspubfnregister_rpc(){staticONCE:std::sync::Once=std::sync::Once::new();ONCE.call_once(||{rpcservice::register_rpc();});}
// rpcservice/register_rpc.rspubfnregister_rpc(){item_service::register_rpc();hello_service::register_rpc();mail_service::register_rpc();}

2.6 新增一个 gRPC 服务

  1. 在rpcservice/voloservice/新建xxx_service.rs:写一行register_rpc!(...),再写impl XxxService for RpcServer。
  2. 在rpcservice/voloservice/mod.rs加pub mod xxx_service;。
  3. 在rpcservice/register_rpc.rs加一行xxx_service::register_rpc();。

无需改动rpc_server.rs——服务端骨架与注册机制完全与服务数量解耦。

命名提示:voloserver/mod.rs的宏名为register_rpc!,它生成的函数也叫
register_rpc();voloserver::rpc_server另有自由函数register_rpc(fn());
rpcrouter::register_rpc与rpcservice::register_rpc各有一个同名函数。
它们处于不同模块作用域,调用时需写全路径以免混淆。

三、客户端 voloclient

RpcClient<C>是通用客户端,C为某服务生成的 client 类型,一套逻辑服务所有服务:

pubstructRpcClient<C>{service_name:&'staticstr,// 逻辑服务名build:fn(&'staticstr,SocketAddr)->C,// client 构建函数cell:OnceLock<C>,// 懒加载缓存}impl<C:Clone>RpcClient<C>{pubfnnew(service_name:&'staticstr,build:fn(&'staticstr,SocketAddr)->C)->Self;pubfnclient(&self)->Result<C,String>;// 取缓存或按配置构建}
  • 地址解析收敛为一个自由函数resolve_target(),读全局配置serverName/serverPort,非法地址回退127.0.0.1
  • OnceLock缓存,每个服务只建一次
  • 新增服务只需RpcClient::new(服务名, |n, a| XxxServiceClientBuilder::new(n).address(a).build())

RpcClients是业务门面(单例 Bean),为每个服务持有一个RpcClient并暴露业务友好方法:

字段通用客户端业务方法
itemRpcClient<ItemServiceClient>get_item(item_id)
helloRpcClient<HelloServiceClient>say_hello(name)
mailRpcClient<MailServiceClient>send_mail(&SendMailDto)

业务方法内部完成 DTO ↔ proto 消息互转,并统一把构建失败转成Status::Unavailable。

四、生命周期插件 rpcplugin

RpcPlugin实现框架Plugintrait,start()依次执行:

  1. init()→plugin_default_init()
  2. register()→rpcrouter::register_rpc::register_rpc()(登记各服务)
  3. RpcServer::new().start()拉起 gRPC 服务

由#[ctor]自动注册为单例,在src/main.rs通过ctx.add(get_bean_unwrap!(RpcPlugin))纳入统一启动流程。

五、服务发现 rpcreg

RpcregPlugin把当前 RPC 服务注册到etcd,复用 webreg 的EtcdReg底层:

  • key:/ruwebframe/rpc/{serverName}
  • value:RpcRegValue(含url=grpc://host:port、serviceType="rpc"等)
  • start_register()异步注册并持续续期,stop_register()注销

如此服务端启动后在 etcd 注册,客户端/网关可据此做服务发现与路由,而不必写死地址。

模块职责小结

模块角色关键类型
rpcplugin生命周期驱动RpcPlugin
rpcreg注册中心RpcregPlugin/RpcEtcdReg
rpcrouter注册汇总入口register_rpc()
rpcservice业务实现(每服务一文件)register_rpc!生成的服务实现
_c voloclient调用方RpcClient<C>/RpcClients
voloserver提供方(骨架 + 容器)RpcServer/RpcContainer/VoloRoutes
main.rs演示入口装配各插件

注册机制对比(与 web 路由对齐)

环节webrurpc
容器RouteContainerVoloRoutes
登记项路由配置服务 codec
自注册各 controller 调用reg_route各服务文件register_rpc!生成的register_rpc()
汇总入口apply_routes(conf)rpcrouter::register_rpc()
注入装配到Appserver.layer(RpcRouterLayer(router))
单例OnceLockOnce + OnceLock/ DI Bean

一图流调用链

[main.rs] ctx.add(RpcregPlugin → 注册到 etcd) ctx.add(RpcPlugin) └─ start ├─ register → rpcrouter::register_rpc()(Once) │ └─ rpcservice::register_rpc() │ ├─ item_service::register_rpc() → voloserver::register_service(codec) │ ├─ hello_service::register_rpc() → voloserver::register_service(codec) │ └─ mail_service::register_rpc() → voloserver::register_service(codec) │ └→ VoloRoutes::register(codec) └─ RpcServer::start → serve ├─ RpcContainer::register_all_services()(当前表为空) ├─ RpcServer::build_server → layer(RpcRouterLayer(VoloRoutes::global().router())) └─ run_with_shutdown └─ Router 分发 → impl ItemService / HelloService / MailService └─ MailService::send ctx.add(WebPlugin) 客户端侧:RpcClients { get_item / say_hello / send_mail } → RpcClient<C>::client()(OnceLock 缓存 + resolve_target) → volo_gen client → 对端 RpcServer
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进