Concurrency 使用共享数据库连接和缓存编写Rust microservice的惯用方法是什么?

Concurrency 使用共享数据库连接和缓存编写Rust microservice的惯用方法是什么?,concurrency,rust,mutex,shared-ptr,Concurrency,Rust,Mutex,Shared Ptr,我正在用hyper编写我的第一个Rust微服务。经过多年的C++和Go开发,我倾向于使用控制器处理请求(如此处-),其中控制器存储共享数据,如数据库连接池和各种缓存。 我知道,使用hyper,我可以这样写: use hyper::{Body, Request, Response}; pub struct Controller { // pub cache: Cache, // pub db: DbConnectionPool } impl Controller { pub

我正在用
hyper
编写我的第一个
Rust
微服务。经过多年的
C++
Go
开发,我倾向于使用控制器处理请求(如此处-),其中控制器存储共享数据,如数据库连接池和各种缓存。 我知道,使用
hyper
,我可以这样写:

use hyper::{Body, Request, Response};

pub struct Controller {
//    pub cache: Cache,
//    pub db: DbConnectionPool
}

impl Controller {
    pub fn echo(&mut self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
        // extensively using db and cache here...
        let mut response = Response::new(Body::empty());
        *response.body_mut() = req.into_body();
        Ok(response)
    }
}
使用hyper::{Body,Request,Response};
发布结构控制器{
//发布缓存:缓存,
//pub db:DbConnectionPool
}
impl控制器{
发布fn回显(&mut self,请求)->结果{
//在这里广泛使用数据库和缓存。。。
让mut response=response::new(Body::empty());
*response.body_mut()=请求进入_body();
Ok(回复)
}
}
然后使用它:

use hyper::{Server, Request, Response, Body, Error};
use hyper::service::{make_service_fn, service_fn};

use std::{convert::Infallible, net::SocketAddr, sync::Arc, sync::Mutex};

async fn route(controller: Arc<Mutex<Controller>>, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    let mut c = controller.lock().unwrap();
    c.echo(req)
}

#[tokio::main]
async fn main() {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    let controller = Arc::new(Mutex::new(Controller{}));

    let make_svc = make_service_fn(move |_conn| {
        let controller = Arc::clone(&controller);
        async move {
            Ok::<_, Infallible>(service_fn(move |req| {
                let c = Arc::clone(&controller);
                route(c, req)
            }))
        }
    });

    let server = Server::bind(&addr).serve(make_svc);

    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}
使用hyper::{Server,Request,Response,Body,Error};
使用hyper::service::{make_service_fn,service_fn};
使用std::{convert::infalleble,net::SocketAddr,sync::Arc,sync::Mutex};
异步fn路由(控制器:Arc,请求:请求)->结果{
设mut c=controller.lock().unwrap();
c、 回声(req)
}
#[tokio::main]
异步fn main(){
让addr=SocketAddr::from([127,0,0,1],3000));
让controller=Arc::new(Mutex::new(controller{}));
让make|u svc=make|u service|fn(移动连接){
let controller=Arc::clone(&controller);
异步移动{
好的::(服务要求){
设c=Arc::clone(&controller);
路由(c,请求)
}))
}
});
让server=server::bind(&addr).service(make_svc);
如果让Err(e)=server.wait{
eprintln!(“服务器错误:{}”,e);
}
}
因为编译器不允许我在线程之间共享可变结构,所以我使用了
Arc
习惯用法。但是我恐怕
让mut c=controller.lock().unwrap()部分将在处理单个请求时阻塞整个控制器,即这里没有并发。

解决此问题的惯用方法是什么?

&mut
始终获取(编译时或运行时)值的独占锁。 仅在要锁定的确切范围内获取
&mut
。 如果锁定值所拥有的值需要单独的锁定管理, 将其包装在一个
互斥体中

假设您的
DbConnectionPool
的结构如下:

struct DbConnectionPool {
    conns: HashMap<ConnId, Conn>,
}
然后,当你想获得连接时

fn get(&self, id: ConnId) -> Arc<Mutex<Conn>> {
    let mut pool = self.db.conns.lock().unwrap(); // ignore error if another thread panicked
    if let Some(conn) = pool.get(id) {
        Arc::clone(conn)
    } else {
        // here we will utilize the interior mutability of `pool`
        let arc = Arc::new(Mutex::new(new_conn()));
        pool.insert(id, Arc::clone(&arc));
        arc
    }
}
为了便于说明,我将逻辑更改为用户提供ID。 实际上,您应该能够找到尚未获取的
Conn
,并返回它。 然后,您可以为
Conn
返回RAII防护, 与
MutexGuard
的工作原理类似, 当用户停止使用连接时自动释放连接


也考虑使用<代码> RwLock <代码>,而不是<代码>互斥> <代码>,如果这可能会导致性能提升。

您使用的是什么代码< >代码连接池> /代码>?如果这些方法不需要
&mut self
,您可以将
echo
更改为
&self
。通常数据库池有自己的锁,您不需要实现自己的锁来使用它们。例如,采用
&self
而不是
&mut self
,即使它可能会向池中添加一个新连接。我知道连接池的特定实现可能是
const
。但我想知道通常该怎么做。例如,让它不是连接池,而是
hashmap
s。然后在hashmap上执行Arc互斥,并在hashmap的每个条目上执行Arc互斥。@谢谢,这对我很有效!你能写下你的评论作为回答吗?
fn get(&self, id: ConnId) -> Arc<Mutex<Conn>> {
    let mut pool = self.db.conns.lock().unwrap(); // ignore error if another thread panicked
    if let Some(conn) = pool.get(id) {
        Arc::clone(conn)
    } else {
        // here we will utilize the interior mutability of `pool`
        let arc = Arc::new(Mutex::new(new_conn()));
        pool.insert(id, Arc::clone(&arc));
        arc
    }
}
self.get(id).lock().unwrap().query(...)