Что такое идиоматический c способ написания микросервиса Rust с совместно используемыми соединениями БД и кешами? - PullRequest
1 голос
/ 01 мая 2020

Я пишу свой первый Rust микросервис с hyper. После многих лет разработки в C++ и Go я склонен использовать контроллер для обработки запросов (как здесь - https://github.com/raycad/go-microservices/blob/master/src/user-microservice/controllers/user.go), где контроллер хранит общие данные, такие как пул соединений db и различные виды кэша. Я знаю, что с 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)
    }
}

и затем использовать его:

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);
    }
}

Поскольку компилятор не позволяет мне совместно использовать изменяемую структуру между темы я должен использовать Arc<Mutex<T>> идиома. Но я боюсь, что часть let mut c = controller.lock().unwrap(); заблокирует весь контроллер при обработке одного запроса, то есть здесь нет параллелизма. Как идиоматический c способ решить эту проблему?

1 Ответ

1 голос
/ 01 мая 2020

&mut всегда получает исключительную блокировку (время компиляции или выполнения) к значению. Приобретайте &mut только в том объеме, который хотите заблокировать. Если значение, которым владеет заблокированное значение, нуждается в отдельном управлении блокировками, оберните его в Mutex.

Предполагая, что ваша DbConnectionPool имеет такую ​​структуру:

struct DbConnectionPool {
    conns: HashMap<ConnId, Conn>,
}

Нам нужно &mut HashMap, когда мы добавляем / удаляем элементы в HashMap, но нам не нужно &mut значение в Conn. Таким образом, Arc позволяет нам отделить границу изменчивости от ее родителя , а Mutex позволяет нам добавить собственную внутреннюю изменчивость .

Более того, наш * Метод 1023 * не хочет быть &mut, поэтому необходимо добавить еще один слой внутренней изменчивости в HashMap.

Так что мы изменим его на

struct DbConnectionPool {
    conns: Mutex<HashMap<ConnId, Arc<Mutex<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
    }
}

(параметр ConnId и логика if-there-else c используются для упрощения кода; вы можете изменить лог c)

На возвращаемое значение вы можете сделать

self.get(id).lock().unwrap().query(...)

. Для удобства я изменил логи c на пользователя, предоставившего идентификатор. На самом деле вы должны быть в состоянии найти Conn, который не был приобретен, и вернуть его. Затем вы можете вернуть защиту RAII для Conn, аналогично тому, как работает MutexGuard, для автоматического освобождения соединения, когда пользователь перестанет его использовать.

Также рассмотрите возможность использования RwLock вместо Mutex, если это может привести к повышению производительности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...