云计算百科
云计算领域专业知识百科平台

Axum:用 Rust 写 Web 后端的正确打开方式

如果你还在用 Express 写 API,或者被 Actix-web 的宏地狱折磨过,这篇文章就是为你准备的。


一、Axum 是什么?

Axum 是 Tokio 团队官方维护的 Rust Web 框架,它的定位非常精准:一个基于 Tower 中间件生态的、类型安全的、极简主义 Web 框架。

它不是什么"瑞士军刀",而是一把打磨到极致的日式厨刀——只做一件事,把它做到最好:处理 HTTP 请求与响应。

  • 开源地址:https://github.com/tokio-rs/axum
  • 许可证:MIT(完全自由商用)
  • 最新稳定版:0.8.x(2026 年 3 月)
  • 项目状态:Tokio 官方维护,社区活跃,CNCF 技术雷达推荐

Axum 的核心设计哲学只有四个字:类型即文档。路由、参数提取、中间件、错误处理——全部用 Rust 类型系统来表达,编译器就是你的第一道防线。


二、为什么选 Axum?四大核心优势

2.1 零宏声明式路由

大多数 Rust Web 框架用宏(macro)来定义路由,比如 Actix-web 的 #[get("/users/{id}")]。这种写法的硬伤在于:宏内部出错时,编译器的报错信息让人想摔键盘。

Axum 直接抛弃了路由宏。路由用纯 Rust 函数链式组合:

let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(get_user).delete(delete_user));

没有任何魔法。你看到的就是真相。IDE 能直接跳转,编译器错误指哪打哪——这对团队协作和大型项目的维护体验是质的变化。

2.2 类型安全的提取器(Extractor)体系

Axum 的"提取器"是它最亮眼的设计。任何从请求中提取数据的行为——路径参数、查询字符串、请求体 JSON、Header——都通过实现 FromRequest trait 来完成。

// 路径参数:/users/{id} → id: i32
async fn get_user(Path(id): Path<i32>) -> impl IntoResponse { … }

// JSON 请求体:自动反序列化 + 400 错误
async fn create_user(Json(payload): Json<CreateUserRequest>) -> impl IntoResponse { … }

// 查询字符串:/search?keyword=rust&page=2
async fn search(Query(params): Query<SearchParams>) -> impl IntoResponse { … }

// 组合提取:一行代码拿到多种数据
async fn update(
State(db): State<DbPool>,
Path(id): Path<i32>,
Json(payload): Json<UpdateRequest>,
) -> Result<Json<User>, AppError> { … }

如果一个 Extractor 组合在类型上不成立,代码根本编译不过。你在 Express 里需要跑到线上才会炸的参数校验错误,在 Axum 里,保存文件那一瞬间就已经被消灭了。

2.3 Tower 中间件生态全面接入

这是一个被严重低估的杀手级优势。

Tower 是 Rust 异步生态的中间件标准,相当于 Java 的 Servlet Filter + Netty 的 ChannelHandler 的合体。Axum 天生兼容整个 Tower 生态——你不需要等框架作者支持某个中间件,社区已经有成百上千个现成的 Tower 中间件可用:

中间件 用途 所需依赖
tower-http::cors CORS 跨域 tower-http = "0.6"
tower-http::compression gzip/brotli 压缩 tower-http = "0.6"
tower-http::trace 请求日志追踪 tower-http = "0.6"
tower::limit::rate 速率限制 tower = "0.5"
tower::timeout 请求超时控制 tower = "0.5"
tower-http::auth 认证/鉴权 tower-http = "0.6"

一行代码挂载中间件:

let app = Router::new()
.route("/api", get(handler))
.layer(TraceLayer::new_for_http()) // 自动记录每个请求
.layer(CompressionLayer::new()) // 自动压缩响应体
.layer(CorsLayer::permissive()) // 开发环境放开 CORS
.layer(TimeoutLayer::new(Duration::from_secs(30))); // 30s 超时

2.4 Tokio 亲儿子——性能与可靠性

Axum 由 Tokio 核心团队开发维护。这意味着什么?

  • 零中间商赚差价:直接运行在 Tokio 异步运行时上,没有抽象层损耗
  • 优先适配:Tokio 的新特性,Axum 总是第一个支持
  • 长期维护承诺:Tokio 是 Rust 异步生态的地基,只要 Tokio 不凉,Axum 就不会凉

实测数据(TechEmpower 2026 基准测试):Axum 在**纯框架(无 ORM)**场景下,单机 QPS 达到 620 万,比 Express.js 快约 18 倍,比 Spring Boot 快约 7 倍。


三、适用场景:什么时候该用 Axum?

3.1 微服务 API 网关 / BFF 层(强烈推荐)

微服务架构中对性能敏感、逻辑较轻的 API 层,是 Axum 的最佳用武之地。

理由:

  • 极低的资源占用(内存 ~10MB 起,对比 Spring Boot ~200MB)
  • 冷启动速度极快(毫秒级,适合 Serverless/容器弹性伸缩)
  • 类型安全的 API 定义,天然适配 OpenAPI / gRPC-transcoding

反例:如果你的服务层重度依赖 Spring 生态组件(如 Spring Security OAuth2 完整实现),那选 Spring Boot 更务实。

3.2 高性能实时通信服务

配合 Tokio 的异步 I/O,Axum 处理 WebSocket 长连接的能力非常亮眼:

async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(|socket| async move {
let (mut sender, mut receiver) = socket.split();
// 双工通信逻辑
})
}

适合聊天服务、游戏服务器、实时数据推送等场景。

3.3 边缘计算 / WASM 部署

Axum 的体积小到可以编译为 WASM 运行在 Cloudflare Workers / Fastly Compute 上,这是 JVM 和 Node.js 无法企及的。

3.4 替代老旧的 Express/Fastify 服务(渐进式迁移)

如果你的 Node.js 服务因为单线程瓶颈撑不住流量了,用 Axum 重写核心热路径是最低成本的优化方案。Rust 的内存安全保证让你不会因为忘了 await 某个 Promise 而炸线上。


四、30 分钟上手:从零到生产级 API

下面带你从 cargo init 开始,搭建一个包含数据库、认证、错误处理的完整 API 服务。

4.1 项目初始化

# Cargo.toml
[package]
name = "my-api"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
tower-http = { version = "0.6", features = ["cors", "trace", "compression"] }
tracing = "0.1"
tracing-subscriber = "0.3"
uuid = { version = "1", features = ["v4"] }
chrono = { version = "0.4", features = ["serde"] }

4.2 定义数据模型

// src/models.rs
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;
use chrono::{DateTime, Utc};

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct User {
pub id: Uuid,
pub name: String,
pub email: String,
pub created_at: DateTime<Utc>,
}

#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub name: String,
pub email: String,
}

#[derive(Debug, Deserialize)]
pub struct ListUsersQuery {
pub page: Option<i64>,
pub per_page: Option<i64>,
}

4.3 构建错误处理体系

// src/error.rs
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;

#[derive(Debug)]
pub enum AppError {
NotFound(String),
BadRequest(String),
Internal(String),
}

impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
};
(status, Json(json!({ "error": message }))).into_response()
}
}

// 将 sqlx::Error 转换为 AppError
impl From<sqlx::Error> for AppError {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound => AppError::NotFound("资源不存在".into()),
_ => AppError::Internal(format!("数据库错误: {}", err)),
}
}
}

4.4 编写 Handler

// src/handlers.rs
use axum::{
extract::{Path, Query, State},
Json,
};
use uuid::Uuid;
use sqlx::PgPool;

use crate::error::AppError;
use crate::models::{CreateUserRequest, ListUsersQuery, User};

// 创建用户
pub async fn create_user(
State(db): State<PgPool>,
Json(payload): Json<CreateUserRequest>,
) -> Result<(axum::http::StatusCode, Json<User>), AppError> {
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *"
)
.bind(&payload.name)
.bind(&payload.email)
.fetch_one(&db)
.await?;

Ok((axum::http::StatusCode::CREATED, Json(user)))
}

// 获取单个用户(按 UUID)
pub async fn get_user(
State(db): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_one(&db)
.await?;

Ok(Json(user))
}

// 分页列表
pub async fn list_users(
State(db): State<PgPool>,
Query(params): Query<ListUsersQuery>,
) -> Result<Json<Vec<User>>, AppError> {
let page = params.page.unwrap_or(1).max(1);
let per_page = params.per_page.unwrap_or(20).min(100);
let offset = (page – 1) * per_page;

let users = sqlx::query_as::<_, User>(
"SELECT * FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2"
)
.bind(per_page)
.bind(offset)
.fetch_all(&db)
.await?;

Ok(Json(users))
}

// 删除用户
pub async fn delete_user(
State(db): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<axum::http::StatusCode, AppError> {
let result = sqlx::query("DELETE FROM users WHERE id = $1")
.bind(id)
.execute(&db)
.await?;

if result.rows_affected() == 0 {
return Err(AppError::NotFound("用户不存在".into()));
}

Ok(axum::http::StatusCode::NO_CONTENT)
}

4.5 组装应用

// src/main.rs
mod error;
mod handlers;
mod models;

use axum::{Router, routing::{delete, get, post}};
use sqlx::PgPool;
use tower_http::{
cors::CorsLayer,
compression::CompressionLayer,
trace::TraceLayer,
};
use tracing_subscriber;

#[tokio::main]
async fn main() {
// 初始化日志
tracing_subscriber::fmt::init();

// 连接数据库(实际项目中改用环境变量)
let db_url = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://localhost/myapp".to_string());
let pool = PgPool::connect(&db_url).await
.expect("数据库连接失败");

// 执行建表(生产环境请用 migration 工具)
sqlx::query(
"CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
)"
)
.execute(&pool)
.await
.expect("建表失败");

// 构建路由
let app = Router::new()
.route("/users", get(handlers::list_users).post(handlers::create_user))
.route("/users/{id}", get(handlers::get_user).delete(handlers::delete_user))
.layer(TraceLayer::new_for_http()) // 请求日志
.layer(CompressionLayer::new()) // 响应压缩
.layer(CorsLayer::permissive()) // CORS
.with_state(pool); // 注入数据库连接池

// 启动服务
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
tracing::info!("服务启动在 http://0.0.0.0:3000");
axum::serve(listener, app).await.unwrap();
}

4.6 测试一下

# 启动
DATABASE_URL=postgres://localhost/myapp cargo run

# 创建用户
curl -X POST http://localhost:3000/users \\
-H "Content-Type: application/json" \\
-d '{"name":"张三","email":"zhangsan@example.com"}'

# 响应:
# {"id":"3fa85f64-5717-4562-b3fc-2c963f66afa6","name":"张三","email":"zhangsan@example.com","created_at":"2026-07-24T08:30:00Z"}

# 获取用户
curl http://localhost:3000/users/3fa85f64-5717-4562-b3fc-2c963f66afa6

# 分页列表
curl "http://localhost:3000/users?page=1&per_page=10"


五、对比:Axum vs Actix-web vs Rocket(2026 版)

维度 Axum 0.8 Actix-web 4.x Rocket 0.5
生态定位 Tokio 亲儿子 独立运行时 独立框架
路由方式 纯函数链式调用 宏 #[get("/")] 宏 #[get("/")]
中间件 Tower 全生态 自有体系 自有 Fairing
异步运行时 Tokio Actix RT Tokio
Rust 版本 稳定版即可 稳定版即可 需要 nightly
学习曲线 平缓(理解 Extractor 即可) 中等 平缓但受 nightly 限制
生产成熟度 Discord / AWS / Cloudflare 大量企业用户 偏社区项目
GitHub Stars 20k+ 31k+ 24k+

我的建议:

  • 新项目 → Axum(没有历史包袱,选未来)
  • 高性能、已有 Actix 经验且不需要 Tower 生态 → Actix-web(依然是性能标杆)
  • 个人项目、愿意用 nightly Rust → Rocket(开发体验最丝滑)
  • 不想折腾,只想赶紧把 API 跑起来 → Axum

六、常见避坑要点

6.1 State 必须是 Clone 的

axum::extract::State 要求其内部类型实现 Clone。数据库连接池(PgPool / MySqlPool)天然支持 Clone,因为它内部是 Arc 包装。但如果传的是自定义结构体,记得派生 Clone。

6.2 不要在 Handler 里做重度计算

Rust 的 async 模型是协作式调度。如果你的 Handler 里有 CPU 密集型操作(图片处理、密码哈希等),请用 tokio::task::spawn_blocking 把它扔到阻塞线程池。

async fn heavy_handler() -> impl IntoResponse {
let result = tokio::task::spawn_blocking(|| {
// 重度计算逻辑
heavy_computation()
})
.await
.unwrap();

Json(result)
}

6.3 错误处理:尽量统一错误类型

上面的示例已经展示了用 AppError 枚举统一错误类型的模式。不要在 Handler 里返回 Result<Json<T>, Box<dyn Error>>——这让下游客户端无法区分 400 和 500,调试起来非常痛苦。

6.4 Middleware 的 layer 顺序决定逻辑

Router::new()
.layer(TimeoutLayer::new(Duration::from_secs(10))) // 最先执行
.layer(TraceLayer::new_for_http()) // 其次
.layer(CompressionLayer::new()) // 最后

layer 的包裹顺序是洋葱模型:最外层 layer 最先处理请求、最后处理响应。超时层必须放在最外层,否则被压缩层挡住,超时控制可能失效。


七、总结

Axum 不是 Rust Web 框架里功能最多的,但它是最"Rust"的那个——充分利用类型系统做编译期检查,拒绝魔法宏,拥抱 Tower 生态,靠 Tokio 团队背书长期维护。

如果你正在做技术选型,或者想从 Node.js / Spring Boot 迁移一部分服务到 Rust,Axum 是 2026 年最稳妥的选择。

用 Axum 写 API,你不需要信任框架,你只需要信任 Rust 编译器。而后者,是整个编程世界里最值得信任的东西。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Axum:用 Rust 写 Web 后端的正确打开方式
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!