微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Actix Web 在空闲时消耗 %5 Cpu

如何解决Actix Web 在空闲时消耗 %5 Cpu

我正在尝试学习 rust atm,因此我使用 actix 和 mongodb 创建了简单的 todo web 应用程序,并通过 docker 部署到 linux 服务器(ubuntu 18.04)。但我意识到,即使没有连接/请求(即容器启动后),cpu 使用率仍保持在 %5-6。

为什么会这样?你有什么想法吗?

cpu usage htop

cpu usage portainer

src/main.rs


#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let logger = slog::Logger::root(
        Mutex::new(slog_json::Json::default(std::io::stderr())).map(slog::Fuse),o!("version" => env!("CARGO_PKG_VERSION"))
    );

    let settings = Settings::new(&logger).unwrap();
    let server_url = format!("0.0.0.0:{}",settings.server.port);
    info!(logger,"Listening on: {}",server_url);

    let http_context = HttpContext{
        logger,repository : Repository::new(&settings).await.unwrap()
    };
    HttpServer::new(move || {
        App::new()
            .wrap(
                StructuredLogger::new(http_context.logger.new(o!("log_type" => "access")))
            )
            .data(web::JsonConfig::default().limit(4096))
            .data(http_context.clone())// <- limit size of the payload (global configuration)
            .service(web::resource("/").route(web::get().to(index)))
            .service(web::resource("/todo/{id}").route(web::get().to(get)))
            .service(web::resource("/todo").route(web::post().to(save)))
    }).bind(server_url)?.run().await
}

src/repository.rs

use mongodb::{Client,options::ClientOptions,error::Error,Collection};
use bson::{doc};
use crate::model::Todo;
use crate::settings::Settings;

#[derive(Clone)]
pub struct Repository {
   collection : Collection,}


impl Repository {
    pub async fn new(settings : &Settings) ->  Result<Self,Error> {
        let mut client_options = ClientOptions::parse(&settings.database.url).await?;
        client_options.app_name = Some("todo-rs".to_string());
        let client = Client::with_options(client_options)?;
        let db = client.database(&settings.database.db_name);
        let collection = db.collection(&settings.database.collection_name);

        Ok(Repository {
            collection
        })
    }

    pub async fn insert(&self,todo: Todo) ->  Result<bool,Error> {
        let serialized_todo = bson::to_bson(&todo)?;
        let document = serialized_todo.as_document().unwrap();


        let insert_result = self.collection.insert_one(document.to_owned(),None).await?;
        let inserted_id = insert_result
            .inserted_id
            .as_object_id().expect("Retrieved _id should have been of type ObjectId");

        dbg!(inserted_id);
        Ok(true)
    }

    pub async fn get(&self,id : &str) ->  Result<Option<Todo>,Error> {
        let result = self.collection.find_one(doc! {"id": id},None).await?;
        match result {
            Some(doc) => {
                Ok(Some(bson::from_bson(bson::Bson::Document(doc)).unwrap()))
            }
            None => {
                Ok(None)
            }
        }
    }

}

src/handler.rs

use actix_web::{web,HttpResponse};
use chrono::prelude::*;
use crate::dto::{SavetodoRequest,IndexResponse,SavetodoResponse};
use crate::model::Todo;
use uuid::Uuid;
use crate::repository::Repository;

#[derive(Clone)]
pub struct HttpContext {
    pub logger : slog::Logger,pub repository: Repository,}


pub async fn index() -> HttpResponse {
    let my_obj = IndexResponse {
        message: "Hello,World!".to_owned(),};
    HttpResponse::Ok().json(my_obj) // <- send response
}

pub async fn save(context: web::Data<HttpContext>,resource: web::Json<SavetodoRequest>) -> HttpResponse {
    let todo_request = resource.into_inner();
    let todo = Todo {
        added_at: Utc::Now(),name: todo_request.name,done: false,tags: todo_request.tags,id: Uuid::new_v4().to_string(),};

    let logger = context.logger.clone();
    let result = context.repository.insert(todo).await;
    match result {
        Ok(result) => {
            info!(logger,"Saved");
            HttpResponse::Ok().json(SavetodoResponse{success:result})
        },Err(e) => {
            error!(logger,"Error while saving,{:?}",e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

pub async fn get(context: web::Data<HttpContext>,id: web::Path<String>) -> HttpResponse {
    let logger = context.logger.clone();
    let result = context.repository.get(&id.into_inner()).await;
    match result {
        Ok(result) => {
            match result {
                Some(r) => HttpResponse::Ok().json(r),None => HttpResponse::NoContent().finish(),}
        },e);
            HttpResponse::InternalServerError().finish()
        }
    }
}

dockerfile

FROM rust:latest as builder
workdir /usr/src/app
copY . .
RUN cargo build --release


FROM debian:buster-slim
RUN apt-get update && apt-get install -y ca-certificates tzdata
ENV TZ=Etc/UTC
workdir /usr/src/app
copY --from=builder /usr/src/app/config config
copY --from=builder /usr/src/app/target/release/todo-rs .

EXPOSE 80
CMD ["./todo-rs"]

解决方法

我在使用 actix 时遇到了同样的问题,后来我切换到了 warp,但我认为问题是一样的。

您可以使用 Arc<Database> 包装您的 mongo 连接,这样当您复制数据库以获取其他数据库时,您将复制引用而不是整个连接器,这会大大提高性能。

此时(2021/2/6),mongodb crate 正在使用旧版本的 reqwest,您可能会遇到 tokio 版本问题(在 0.2 和 1.0 之间)。您可以使用主分支的最新版本修复该问题,并将其放在您的依赖项上:

mongodb = { git = "https://github.com/mongodb/mongo-rust-driver/" }

旧版本

我认为 mongo-driver 的每个克隆都消耗 cpu。所以你需要做的是在你的程序中有一个与数据库通信的点。您可以使用 tokio 频道来实现这一点。

这就是我所拥有的:

use tokio::sync::mpsc;
...
let (tx,rx) = mpsc::channel::<DBCommand>(32);
tokio::spawn( async move{
    db_channel(rx).await;        
});
let routes = filters::filters(tx);
warp::serve(routes).run(([127,1],3030)).await;

因此,在 warp 中,您应该将您的发射器作为 AppState 传递,这样您就可以像这样将消息发送到 db 通道:

tx.send(/* channel struct /*).await.unwrap();

这份指南对我来说已经足够了https://tokio.rs/tokio/tutorial/channels。此外,您需要将 oneshot 发送器传递到 db 通道以恢复来自 db 的响应。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。