Rust Salvo框架多数据库连接池管理及并发阻塞问题咨询
问题描述
我正在用Rust的Salvo Web框架构建应用,需要支持多种数据库连接类型(无TLS的Postgres、带TLS的Postgres、无TLS的Redis、带TLS的Redis),配置由用户通过配置文件定义。每种连接类型可创建N个实例,每个实例有独立连接池,能通过名称引用。
初始实现代码
连接定义与池创建逻辑
use core::panic; use std::collections::HashMap; use bb8_postgres::PostgresConnectionManager; use serde_json::Value; use tokio_postgres::NoTls; use bb8::Pool; pub struct AnyConnection { name: String, details: HashMap<String, Value>, secure: bool } impl AnyConnection { pub fn new(name: String, details: HashMap<String, Value>, secure: bool) -> Self { AnyConnection { name, details, secure } } } #[salvo::async_trait] pub trait PostgresPool { async fn make_unsecure(&self) -> Option<Pool<PostgresConnectionManager<NoTls>>>; } #[salvo::async_trait] impl PostgresPool for AnyConnection { async fn make_unsecure(&self) -> Option<Pool<PostgresConnectionManager<NoTls>>> { let manager = PostgresConnectionManager::new_from_stringlike( &self.details.get("DATABASE_URL").unwrap().to_string(), tokio_postgres::NoTls, ); let conn_manager = match manager { Ok(m) => m, Err(err) => panic!("Error opening a connection for: {} with error: {:?}", &self.name, err.to_string()) }; let pool = match Pool::builder().max_size(5).build(conn_manager).await { Ok(p) => p, Err(err) => panic!("Error creating pool for: {} with error: {:?}", &self.name, err.to_string()) }; Some(pool) } }
初始化代码(main.rs)
let mut conns: HashMap<String, AnyConnection> = HashMap::new(); let mut details: HashMap<String, Value> = HashMap::new(); details.insert("DATABASE_URL".to_string(), Value::String("postgresql://localhost:5432/customers?user=postgres&password=postgres".to_string())); let customers_db: AnyConnection = AnyConnection::new("customers_db".to_string(), details, false); let pg = PostgresPool::make_unsecure(&customers_db).await;
初始疑问
- 存储连接池对象引用的
connsHashMap应该缓存到哪里? - 连接是否会安全归还到该HashMap中存储的连接池?
更新内容与新问题
根据反馈改写了处理器代码:
#[async_trait] impl Handler for HandlerWithPool { async fn handle(&self, req: &mut Request, _depot: &mut Depot, res: &mut Response, _ctrl: &mut FlowCtrl) { let delay: Option<u64> = req.query::<u64>("delay"); // self.db_pools is available here let db_pools = self.get_pools(); let pgpool = db_pools.get("pg").unwrap(); let pgconn = match pgpool { AnyConn::Postgres(p) => { println!("Pool State: {:?}", p.state()); // 显示多个连接 Some(p) }, _ => None }; let rows = pgconn.unwrap().get().await.unwrap().query("select * from users where id = $1", &[&"User-123"]).await.unwrap(); for row in rows { //println!("Row {:?}", row); } let dur = time::Duration::from_millis(delay.unwrap() * 1000); thread::sleep(dur); res.render("Hello World!"); } }
为测试连接池并发能力,添加了delay查询参数。发送第一个延迟10秒的请求后,第二个延迟1秒的请求需要等待第一个请求完成才执行,看起来连接池被锁定导致阻塞。另外说明:所有连接池均在服务器启动时创建,无需动态生成。
解答
一、连接池的缓存位置
在Salvo框架中,推荐两种全局缓存方式:
- 全局单例:用
once_cell::sync::Lazy或lazy_static定义全局的HashMap<String, AnyConn>枚举类型,包含不同数据库的连接池实例。因为连接池是启动时一次性创建的,全局单例能让所有处理器直接访问,逻辑简单高效。 - Salvo全局Depot:在
main函数中通过Service::new().depot()将连接池存入全局Depot,后续任意处理器都可通过&mut Depot取出使用,符合Salvo的请求上下文设计。
不建议仅在局部持有conns HashMap,这样无法实现跨处理器的连接池共享,不符合全局资源的使用逻辑。
二、连接的安全归还
bb8连接池会自动处理连接归还:当通过get().await获取的bb8::PooledConnection对象离开作用域被Drop时,bb8会自动将连接归还到池中,无需手动操作。只要你缓存的是全局唯一的Pool实例,连接就会安全归还到对应池内。
三、并发阻塞问题的原因与解决
你的代码阻塞的核心原因是使用了同步阻塞的thread::sleep(dur),它会占用当前Tokio工作线程,导致其他请求无法被调度处理。同时,这段代码会让获取到的数据库连接被持有10秒,快速耗尽连接池中的可用连接,加剧阻塞。
解决步骤:
- 替换同步睡眠为Tokio异步延迟:
use tokio::time::{sleep, Duration}; // 替换原来的thread::sleep let dur = Duration::from_millis(delay.unwrap() * 1000); sleep(dur).await;
- 确保连接池是全局唯一实例,避免重复创建独立池。
- 尽量缩短连接持有时间:如果延迟操作不需要数据库连接,可将
sleep(dur).await放在数据库操作完成之后,让连接尽早归还到池中。
内容的提问来源于stack exchange,提问作者ed1t
相关产品推荐
相关产品推荐

