Rust语言凭借零成本抽象和内存安全特性,在高性能后端服务领域逐渐替代C++和Go。Axum是Tokio团队开发的Web框架,基于Hyper和Tower生态构建,提供类型安全的路由、中间件和提取器抽象。配合SQLx异步数据库驱动,Rust后端服务在吞吐量和延迟控制上具有显著优势。
Axum框架架构与Tokio异步运行时
Axum的核心设计理念是组合优于继承。框架本身不定义全局状态管理或中间件链,而是通过Tower的Service trait组合功能。Tokio异步运行时提供多线程调度、异步IO和定时器支持。
项目初始化:
# 创建项目
cargo new rust-api
cd rust-api
# Cargo.toml
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio", "chrono", "uuid"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower = "0.5"
tower-http = { version = "0.6", features = ["trace", "cors", "timeout"] }
tracing = "0.1"
tracing-subscriber = "0.3"
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1"
thiserror = "1"
基础服务搭建:
use axum::{Router, routing::get, routing::post, Server};
use tower_http::trace::TraceLayer;
use tower_http::cors::CorsLayer;
use tower_http::timeout::TimeoutLayer;
use std::time::Duration;
#[tokio::main]
async fn main() {
// 初始化日志
tracing_subscriber::fmt::init();
// 初始化数据库连接池
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(20)
.min_connections(5)
.acquire_timeout(Duration::from_secs(3))
.idle_timeout(Duration::from_secs(600))
.connect("postgres://user:pass@localhost:5432/appdb")
.await
.expect("数据库连接失败");
// 运行数据库迁移
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
// 构建路由
let app = Router::new()
.route("/health", get(health_check))
.route("/api/users", post(create_user).get(list_users))
.route("/api/users/:id", get(get_user).put(update_user).delete(delete_user))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http())
.with_state(pool);
// 启动服务
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
tracing::info!("服务启动: http://0.0.0.0:3000");
Server::from(listener).serve(app.into_make_service()).await.unwrap();
}
async fn health_check() -> &'static str {
"OK"
}
路由定义与提取器链式处理
Axum的提取器(Extractor)是类型安全的请求参数解析机制。函数参数的类型决定了如何从HTTP请求中提取数据,编译期检查参数类型,运行时自动解析。
use axum::{
extract::{State, Path, Query},
response::{Json, IntoResponse, Response},
http::StatusCode,
};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;
use chrono::Utc;
#[derive(Debug, Serialize)]
struct User {
id: Uuid,
name: String,
email: String,
created_at: chrono::DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
#[derive(Debug, Deserialize)]
struct ListUsersQuery {
page: Option<u32>,
page_size: Option<u32>,
}
#[derive(Debug, Deserialize)]
struct UpdateUserRequest {
name: Option<String>,
email: Option<String>,
}
// 提取器按顺序匹配:State -> Path -> Query -> Json
async fn get_user(
State(pool): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as!(
User,
"SELECT id, name, email, created_at FROM users WHERE id = $1",
id
)
.fetch_optional(&pool)
.await?
.ok_or(AppError::NotFound("用户不存在".into()))?;
Ok(Json(user))
}
async fn list_users(
State(pool): State<PgPool>,
Query(query): Query<ListUsersQuery>,
) -> Result<Json<Vec<User>>, AppError> {
let page = query.page.unwrap_or(1) as i64;
let page_size = query.page_size.unwrap_or(20) as i64;
let offset = (page - 1) * page_size;
let users = sqlx::query_as!(
User,
"SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2",
page_size, offset
)
.fetch_all(&pool)
.await?;
Ok(Json(users))
}
async fn create_user(
State(pool): State<PgPool>,
Json(req): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<User>), AppError> {
let user = sqlx::query_as!(
User,
r#"INSERT INTO users (id, name, email, created_at)
VALUES ($1, $2, $3, $4)
RETURNING id, name, email, created_at"#,
Uuid::new_v4(), req.name, req.email, Utc::now()
)
.fetch_one(&pool)
.await
.map_err(|e| match e {
sqlx::Error::Database(ref db_err) if db_err.is_unique_violation() => {
AppError::Conflict("邮箱已存在".into())
}
_ => AppError::from(e)
})?;
Ok((StatusCode::CREATED, Json(user)))
}
async fn update_user(
State(pool): State<PgPool>,
Path(id): Path<Uuid>,
Json(req): Json<UpdateUserRequest>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as!(
User,
r#"UPDATE users SET
name = COALESCE($1, name),
email = COALESCE($2, email)
WHERE id = $3
RETURNING id, name, email, created_at"#,
req.name, req.email, id
)
.fetch_optional(&pool)
.await?
.ok_or(AppError::NotFound("用户不存在".into()))?;
Ok(Json(user))
}
async fn delete_user(
State(pool): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<StatusCode, AppError> {
let result = sqlx::query!("DELETE FROM users WHERE id = $1", id)
.execute(&pool)
.await?;
if result.rows_affected() == 0 {
return Err(AppError::NotFound("用户不存在".into()));
}
Ok(StatusCode::NO_CONTENT)
}
统一错误处理与响应封装
Axum通过IntoResponse trait实现统一的错误响应。定义项目级错误类型,将数据库错误、业务错误统一转换为HTTP响应:
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("资源不存在: {0}")]
NotFound(String),
#[error("参数冲突: {0}")]
Conflict(String),
#[error("参数校验失败: {0}")]
BadRequest(String),
#[error(transparent)]
Database(#[from] sqlx::Error),
#[error(transparent)]
Internal(#[from] anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_code, message) = match &self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, 404, msg.clone()),
AppError::Conflict(msg) => (StatusCode::CONFLICT, 409, msg.clone()),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, 400, msg.clone()),
AppError::Database(e) => {
tracing::error!("数据库错误: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, 500, "内部错误".into())
}
AppError::Internal(e) => {
tracing::error!("内部错误: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, 500, "内部错误".into())
}
};
let body = serde_json::json!({
"error": {
"code": error_code,
"message": message,
}
});
(status, Json(body)).into_response()
}
}
中间件开发与请求日志追踪
自定义中间件通过Tower的Layer和Service trait实现。以下实现请求ID注入和耗时统计中间件:
use axum::{middleware::Next, extract::Request, response::Response};
use uuid::Uuid;
use std::time::Instant;
async fn request_id_middleware(mut req: Request, next: Next) -> Response {
let request_id = req
.headers()
.get("x-request-id")
.and_then(|v| v.to_str().ok())
.map(String::from)
.unwrap_or_else(|| Uuid::new_v4().to_string());
req.headers_mut().insert(
"x-request-id",
request_id.parse().unwrap(),
);
let start = Instant::now();
let mut response = next.run(req).await;
let elapsed = start.elapsed();
response.headers_mut().insert(
"x-request-id",
request_id.parse().unwrap(),
);
response.headers_mut().insert(
"x-response-time",
format!("{}ms", elapsed.as_millis()).parse().unwrap(),
);
tracing::info!(
"请求完成 耗时={}ms",
elapsed.as_millis()
);
response
}
// 注册中间件(顺序:从外到内)
let app = Router::new()
.route("/api/users", get(list_users).post(create_user))
.layer(axum::middleware::from_fn(request_id_middleware))
.layer(TraceLayer::new_for_http())
.with_state(pool);
性能基准测试与生产部署
使用wrk进行HTTP基准测试,对比Rust Axum与Go Gin在相同业务逻辑下的性能表现:
# 安装wrk
git clone https://github.com/wg/wrk.git
cd wrk && make
# 基准测试(4线程,100连接,30秒)
wrk -t4 -c100 -d30s http://localhost:3000/api/users?page=1&page_size=20
# 测试结果示例
# Axum (Rust):
# Requests/sec: 78521.33
# Latency: 1.27ms (P50), 2.43ms (P99)
# Transfer/sec: 12.45MB
#
# Gin (Go):
# Requests/sec: 62104.18
# Latency: 1.61ms (P50), 3.87ms (P99)
# Transfer/sec: 9.84MB
生产部署推荐使用Docker多阶段构建,减小镜像体积:
# Dockerfile
FROM rust:1.75-slim as builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src ./src
COPY migrations ./migrations
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/rust-api /usr/local/bin/
COPY --from=builder /app/migrations /app/migrations
EXPOSE 3000
CMD ["rust-api"]
SQLx的连接池配置对性能影响显著。max_connections不宜过大,通常设置为CPU核心数的4-8倍。PostgreSQL默认max_connections=100,所有服务实例的连接池总和不应超过该值的80%,预留连接给运维查询和迁移操作。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/rust-hou-duan-fu-wu-kai-fa-shi-zhan-axum-kuang-jia-yi-bu/