docker - Actix Web 在空闲时消耗 %5 Cpu
问题描述
我正在尝试学习 rust atm,因此我使用 actix 和 mongodb 创建了简单的 todo Web 应用程序,并通过 docker 部署到 linux 服务器(ubuntu 18.04)。但我意识到,即使没有连接/请求(即容器启动后),cpu 使用率也保持在 %5-6。
为什么会这样?你有什么主意吗?
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(),
}
},
Err(e) => {
error!(logger,"Error while saving, {:?}", 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 但我认为问题是一样的。
新的
您可以使用这样来包装您的 mongo 连接Arc<Database>
,当您复制数据库以获取其他数据库时,您复制的是引用而不是整个连接器,这会大大提高性能。
目前(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, 0, 0, 1], 3030)).await;
所以在warp中你应该将你的发射器作为AppState传递,这样你就可以像这样向db通道发送消息:
tx.send(/* channel struct /*).await.unwrap();
本指南对我来说已经足够了https://tokio.rs/tokio/tutorial/channels。此外,您需要将 oneshot 发送器传递到 db 通道以恢复来自 db 的响应。
推荐阅读
- flutter - 有没有办法在 GoogleMap 上显示“比例尺”?
- markdown - 使用 r-exams 包含 circuitikz 包
- php - 在 WP 管理员中更改自定义帖子类型的列标题名称
- prolog - Prolog - 程序跳过阅读?
- templates - Ansible 模板:试图索引到一个两级列表
- swift - 如果在应用程序内部扫描了条码,则捕获
- python - 如何查找并打印数组中大于某个数字的元素?
- mysql - MySQL 输出列基于与最大 ID 对应的值
- amazon-web-services - 修补后 EC2 实例 IAM 角色更改
- javascript - 如何使用“mysite.com/?search=words”概念在返回和第四次导航时记住过滤/搜索结果页面停留的位置?