如何在同一Tonic服务器的两个服务间共享可修改通用变量?
问题
我正在验证Tonic是否适配业务场景,同时编写Hello World示例测试。目前定义了两个proto文件:
helloworld.proto:实现Greeter服务,消息结构和官方Hello World示例一致wonderfulworld.proto:实现Wonderful服务,消息结构类似,但请求无参数且返回内容不同
当前通过以下代码启动服务:
Server::builder() .add_service(GreeterServer::new(greeter)) .add_service(WonderfulServer::new(wonderful)) .serve(addr) .await?;
现在需要让Greeter和Wonderful两个服务共用并修改一个通用变量(比如world_name),尝试过三种方案但都有问题:
- 在同一个结构体上同时实现
Wonderful和Greetertrait,但new()是值传递,无法链式调用add_service - 创建包含
world_name的MyServer结构体,但找不到对应的添加服务API - 使用全局单例模式,虽然能运行但不是理想方案
附当前完整server.rs代码:
use tonic::{transport::Server, Request, Response, Status}; use hello_wonderful_world::greeter_server::{Greeter, GreeterServer}; use hello_wonderful_world::{HelloReply, HelloRequest}; use hello_wonderful_world::wonderful_server::{Wonderful, WonderfulServer}; use hello_wonderful_world::{WonderfulRequest, WonderfulReply}; pub mod hello_wonderful_world { tonic::include_proto!("helloworld"); tonic::include_proto!("wonderfulworld"); } #[derive(Debug, Default)] pub struct MyGreeter {} #[derive(Debug, Default)] pub struct MyWonderfulWorld{} #[tonic::async_trait] impl Greeter for MyGreeter { async fn say_hello( &self, request: Request<HelloRequest>, ) -> Result<Response<HelloReply>, Status> { println!("Got a request: {:?}", request); let reply = hello_wonderful_world::HelloReply { message: format!("Hello {}!", request.into_inner().name).into(), }; Ok(Response::new(reply)) } } #[tonic::async_trait] impl Wonderful for MyWonderfulWorld { async fn its_a_wonderful_world( &self, request: Request<WonderfulRequest>) -> Result<Response<WonderfulReply>, Status> { let reply = hello_wonderful_world::WonderfulReply { message: format!("And I said to {}: What a Wonderful World", request.into_inner().name), }; Ok(Response::new(reply)) } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let addr = "[::1]:50051".parse()?; let greeter = MyGreeter::default(); let my_wonderful = MyWonderfulWorld::default(); Server::builder() .add_service(GreeterServer::new(greeter)) .add_service(WonderfulServer::new(my_wonderful)) .serve(addr) .await?; Ok(()) }
推荐解决方案
完全可行,核心思路是利用内部可变性和共享所有权实现两个服务共用可变状态。具体用Arc<Mutex<...>>(异步场景推荐Arc<RwLock<...>>优化性能)包裹共享变量,让两个服务结构体持有同一个Arc实例,即可在各自的trait实现中访问和修改共享状态。
修改后的完整代码
use std::sync::{Arc, Mutex}; use tonic::{transport::Server, Request, Response, Status}; use hello_wonderful_world::greeter_server::{Greeter, GreeterServer}; use hello_wonderful_world::{HelloReply, HelloRequest}; use hello_wonderful_world::wonderful_server::{Wonderful, WonderfulServer}; use hello_wonderful_world::{WonderfulRequest, WonderfulReply}; pub mod hello_wonderful_world { tonic::include_proto!("helloworld"); tonic::include_proto!("wonderfulworld"); } // 定义共享状态结构体 #[derive(Debug, Default)] pub struct SharedState { world_name: Mutex<String>, } // Greeter服务持有共享状态的Arc #[derive(Debug, Clone)] pub struct MyGreeter { state: Arc<SharedState>, } // Wonderful服务同样持有共享状态的Arc #[derive(Debug, Clone)] pub struct MyWonderfulWorld { state: Arc<SharedState>, } #[tonic::async_trait] impl Greeter for MyGreeter { async fn say_hello( &self, request: Request<HelloRequest>, ) -> Result<Response<HelloReply>, Status> { println!("Got a request: {:?}", request); // 访问并修改共享的world_name let mut world_name = self.state.world_name.lock().map_err(|e| { Status::internal(format!("Failed to lock state: {}", e)) })?; // 示例:根据请求修改world_name *world_name = request.into_inner().name.clone(); let reply = hello_wonderful_world::HelloReply { message: format!("Hello {}!", world_name).into(), }; Ok(Response::new(reply)) } } #[tonic::async_trait] impl Wonderful for MyWonderfulWorld { async fn its_a_wonderful_world( &self, request: Request<WonderfulRequest>, ) -> Result<Response<WonderfulReply>, Status> { // 读取共享的world_name let world_name = self.state.world_name.lock().map_err(|e| { Status::internal(format!("Failed to lock state: {}", e)) })?; let reply = hello_wonderful_world::WonderfulReply { message: format!("And I said to {}: What a Wonderful World", world_name), }; Ok(Response::new(reply)) } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let addr = "[::1]:50051".parse()?; // 创建共享状态并包装成Arc let shared_state = Arc::new(SharedState { world_name: Mutex::new("Default World".to_string()), }); // 初始化两个服务,传入同一个Arc实例 let greeter = MyGreeter { state: shared_state.clone(), }; let my_wonderful = MyWonderfulWorld { state: shared_state, }; Server::builder() .add_service(GreeterServer::new(greeter)) .add_service(WonderfulServer::new(my_wonderful)) .serve(addr) .await?; Ok(()) }
关键说明
- 共享状态管理:
Arc实现多所有者共享,Mutex保证可变访问的线程安全(Tonic服务会在多线程环境处理请求) - 服务结构体设计:两个服务结构体都持有
Arc<SharedState>,通过clone()复制Arc仅复制指针,不复制内部数据 - 状态访问:在trait方法中通过
lock()获取Mutex访问权,需处理锁可能的poisoning错误,转换成gRPC的Status返回 - 异步优化:纯异步场景推荐用
tokio::sync::RwLock替代std::sync::Mutex,避免阻塞异步任务
内容的提问来源于stack exchange,提问作者Korydween
相关产品推荐
相关产品推荐

