云计算百科
云计算领域专业知识百科平台

ruwebframe全家桶:rurpc、rurpcframe

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 个参与编译的服务:

proto 文件package逻辑服务名
volo_hello.proto hello hello.HelloService、hello.ItemService
volo_mail.proto mail 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 服务

  • 在 volo-gen/idl/ 新增 volo_xxx.proto,并在 volo-gen/build.rs 加一行
    add_service("./idl/volo_xxx.proto"),按其定义生成 client(cargo build 触发)。
  • 在 rurpc/rpcservice/voloservice/ 新建 xxx_service.rs:写一行 register_rpc!(…),
    再写 impl XxxService for VoloApp。
  • 在 rurpc/rpcservice/voloservice/mod.rs 加 pub mod xxx_service;。
  • 在 rurpc/rpcservice/register_rpc.rs 加一行 xxx_service::register_rpc();。
  • (可选,如需客户端调用)在 rurpc/voloclients/rpc_clients.rs 的 RpcClients::new()
    增加一个 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()。它们处于不同模块作用域,调用时需写全路径以免混淆。

    注册机制对比

    环节webrurpc(业务层)rurpcframe(框架层)
    路由容器 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

    一图流目录职责小结

    模块(框架层 rurpcframe)角色关键类型
    voloserver 服务端骨架 + 容器 + 路由 RpcServer / RpcContainer / VoloRoutes
    rpcplugin 生命周期驱动(反向注册) RpcPlugin
    rpcreg 注册中心 RpcregPlugin / RpcEtcdReg / RpcRegValue
    rpcclient 通用客户端 RpcClient<C>
    模块(业务层 rurpc)角色关键类型
    rpcservice/voloservice 业务实现(每服务一文件) register_rpc! 生成的服务实现
    rpcservice/voloapp 统一服务实现体 VoloApp
    voloclients 调用方门面 RpcClients(包装多个 RpcClient<C>)
    rpcrouter 反向注册入口 register_client()
    main.rs 演示装配入口 装配 RpcPlugin / RpcregPlugin
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » ruwebframe全家桶:rurpc、rurpcframe
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!