为什么 Rust 正在重塑后端开发格局

过去几年里,Rust 连续蝉联 Stack Overflow "最受开发者喜爱的编程语言"。但 Rust 的应用远不只是系统编程和区块链——在 2026 年的后端开发领域,Rust 凭借零成本抽象、内存安全和接近 C++ 的性能,正在成为 Go、Java 之外不可忽视的新选择。尤其是在高并发、低延迟场景中,Rust 展现出了独特的竞争力。

本文将带你从零开始,用 Rust 生态中人气最高的 Web 框架 Axum,构建一个生产级别的 RESTful API 服务。我们会涵盖:异步运行时选型(Tokio)、中间件设计、数据库集成(SQLx + PostgreSQL)、错误处理统一化、JWT 认证与授权、API 文档自动化(utoipa)、性能压测对比(Go/Gin vs Rust/Axum),以及 Docker 化部署策略。

技术栈概览

层级 技术选择 说明
异步运行时TokioRust 生态最成熟的 async runtime,多线程工作窃取调度
Web 框架Axum 0.8Tower 生态原生框架,中间件组合能力强
数据库SQLx + PostgreSQL编译期 SQL 检查,运行时安全
序列化SerdeRust 标准序列化库,性能顶级
认证jsonwebtoken + bcryptJWT + 密码哈希
文档utoipa + utoipa-swagger-ui从代码自动生成 OpenAPI 3.0 文档
日志追踪tracing + tracing-subscriber结构化异步日志

环境搭建与项目初始化

开始之前,确保已安装 Rust 工具链(rustup):

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update
cargo install cargo-watch  # 开发时热重载
cargo install sqlx-cli     # 数据库迁移工具

创建项目:

cargo init rust-axum-api
cd rust-axum-api

Cargo.toml 依赖配置

[package]
name = "rust-axum-api"
version = "0.1.0"
edition = "2024"

[dependencies]
# Web 框架
axum = "0.8"
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace", "compression"] }

# 异步运行时
tokio = { version = "1", features = ["full"] }

# 序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# 数据库
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "uuid"] }
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }

# 认证
jsonwebtoken = "9"
bcrypt = "0.16"

# 日志与追踪
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# 错误处理
thiserror = "2"
anyhow = "1"

# 配置管理
dotenvy = "0.15"
config = "0.15"

# API 文档
utoipa = { version = "5", features = ["chrono", "uuid"] }
utoipa-swagger-ui = { version = "8", features = ["axum"] }

项目架构设计

一个清晰的项目结构对 Rust 项目至关重要,它直接影响编译速度和维护成本:

rust-axum-api/
├── Cargo.toml
├── migrations/              # SQLx 数据库迁移文件
│   └── 001_init.sql
├── src/
│   ├── main.rs              # 应用入口
│   ├── lib.rs               # 库入口
│   ├── config.rs            # 配置管理
│   ├── db.rs                # 数据库连接池
│   ├── error.rs             # 统一错误类型
│   ├── middleware/           # 中间件
│   │   ├── mod.rs
│   │   └── auth.rs          # JWT 认证中间件
│   ├── routes/              # 路由模块
│   │   ├── mod.rs
│   │   ├── auth.rs          # 认证路由
│   │   └── users.rs         # 用户 CRUD 路由
│   ├── models/              # 数据模型
│   │   ├── mod.rs
│   │   └── user.rs          # User 结构体
│   ├── services/            # 业务逻辑层
│   │   ├── mod.rs
│   │   └── user_service.rs
│   └── docs.rs              # OpenAPI 文档
└── .env                     # 环境变量

核心代码实战

1. 统一错误处理

在 Rust 中,错误处理是一等公民。Axum 的优雅之处在于它允许我们将错误类型直接作为返回值,只要实现了 IntoResponse trait:

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("数据库错误: {0}")]
    DatabaseError(#[from] sqlx::Error),

    #[error("认证失败: {0}")]
    AuthError(String),

    #[error("未找到: {0}")]
    NotFound(String),

    #[error("验证失败: {0}")]
    ValidationError(String),

    #[error("内部服务器错误")]
    InternalError,
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, error_message) = match &self {
            AppError::DatabaseError(_) => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
            AppError::AuthError(msg) => (StatusCode::UNAUTHORIZED, msg.clone()),
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
            AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
            AppError::InternalError => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
        };

        let body = Json(json!({
            "error": error_message,
            "code": status.as_u16()
        }));

        (status, body).into_response()
    }
}

2. 数据库连接池与 SQLx 编译期查询检查

sqlx 的最大亮点在于编译期 SQL 验证——它在编译时连接数据库检查 SQL 语句的正确性,将运行时错误提前到编译期暴露:

use sqlx::postgres::{PgPool, PgPoolOptions};
use std::time::Duration;

pub async fn create_pool(database_url: &str) -> Result {
    PgPoolOptions::new()
        .max_connections(10)
        .min_connections(2)
        .acquire_timeout(Duration::from_secs(5))
        .idle_timeout(Duration::from_secs(300))
        .connect(database_url)
        .await
}

SQLx 迁移文件示例:

-- migrations/001_init.sql
CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);

3. User 模型与 Serde 序列化

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Serialize, FromRow, ToSchema)]
pub struct UserResponse {
    pub id: Uuid,
    pub username: String,
    pub email: String,
    pub created_at: DateTime,
    pub updated_at: DateTime,
}

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

#[derive(Debug, Deserialize, ToSchema)]
pub struct LoginRequest {
    pub email: String,
    pub password: String,
}

#[derive(Debug, Serialize, ToSchema)]
pub struct LoginResponse {
    pub token: String,
    pub token_type: String,
    pub expires_in: i64,
    pub user: UserResponse,
}

4. JWT 认证中间件

use axum::{
    extract::{Request, State},
    middleware::Next,
    response::Response,
};
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
use serde::Deserialize;
use crate::error::AppError;

#[derive(Debug, Deserialize)]
pub struct Claims {
    pub sub: String,    // 用户 ID
    pub exp: usize,     // 过期时间
    pub iat: usize,     // 签发时间
}

#[derive(Clone)]
pub struct AuthConfig {
    pub secret: String,
}

pub async fn auth_middleware(
    State(config): State,
    mut request: Request,
    next: Next,
) -> Result {
    let auth_header = request
        .headers()
        .get("authorization")
        .and_then(|h| h.to_str().ok())
        .and_then(|h| h.strip_prefix("Bearer "))
        .ok_or_else(|| AppError::AuthError("缺少认证令牌".to_string()))?;

    let token_data = decode::(
        auth_header,
        &DecodingKey::from_secret(config.secret.as_bytes()),
        &Validation::new(Algorithm::HS256),
    )
    .map_err(|e| AppError::AuthError(format!("无效令牌: {}", e)))?;

    request.extensions_mut().insert(token_data.claims.sub);
    Ok(next.run(request).await)
}

5. Axum 路由与 Handler 实现

use axum::{
    extract::{Path, State},
    routing::{get, post, put, delete},
    Router,
};
use uuid::Uuid;

pub async fn create_user(
    State(state): State,
    Json(req): Json,
) -> Result<(StatusCode, Json), AppError> {
    if req.username.len() < 3 xss=removed xss=removed>(
        r#"
        INSERT INTO users (username, email, password_hash)
        VALUES ($1, $2, $3)
        RETURNING id, username, email, created_at, updated_at
        "#,
    )
    .bind(&req.username)
    .bind(&req.email)
    .bind(&password_hash)
    .fetch_one(&state.pool)
    .await?;

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

pub async fn list_users(
    State(state): State,
    axum::extract::Query(params): axum::extract::Query,
) -> Result>, AppError> {
    let limit = params.limit.unwrap_or(20).min(100);
    let offset = params.offset.unwrap_or(0);

    let users = sqlx::query_as::<_, UserResponse>(
        r#"
        SELECT id, username, email, created_at, updated_at
        FROM users
        ORDER BY created_at DESC
        LIMIT $1 OFFSET $2
        "#,
    )
    .bind(i64::from(limit))
    .bind(i64::from(offset))
    .fetch_all(&state.pool)
    .await?;

    Ok(Json(users))
}

pub fn user_routes() -> Router {
    Router::new()
        .route("/users", post(create_user).get(list_users))
        .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
}

6. JWT 签发——登录 Handler

use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey, Header, Algorithm};

pub async fn login(
    State(state): State,
    Json(req): Json,
) -> Result, AppError> {
    let user = sqlx::query_as::<_, UserWithPassword>(
        r#"
        SELECT id, username, email, password_hash, created_at, updated_at
        FROM users WHERE email = $1
        "#,
    )
    .bind(&req.email)
    .fetch_optional(&state.pool)
    .await?
    .ok_or_else(|| AppError::AuthError("邮箱或密码错误".into()))?;

    let valid = bcrypt::verify(req.password.as_bytes(), &user.password_hash)
        .map_err(|_| AppError::InternalError)?;

    if !valid {
        return Err(AppError::AuthError("邮箱或密码错误".into()));
    }

    let claims = Claims {
        sub: user.id.to_string(),
        exp: (Utc::now() + Duration::hours(24)).timestamp() as usize,
        iat: Utc::now().timestamp() as usize,
    };

    let token = encode(
        &Header::new(Algorithm::HS256),
        &claims,
        &EncodingKey::from_secret(state.jwt_secret.as_bytes()),
    )
    .map_err(|_| AppError::InternalError)?;

    Ok(Json(LoginResponse {
        token,
        token_type: "Bearer".into(),
        expires_in: 86400,
        user: user.into(),
    }))
}

7. 应用启动与中间件栈

use axum::Router;
use tower_http::{
    cors::{Any, CorsLayer},
    compression::CompressionLayer,
    trace::TraceLayer,
};
use std::net::SocketAddr;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt::init();

    let config = Config::from_env()?;
    let pool = create_pool(&config.database_url).await?;

    sqlx::migrate!("./migrations")
        .run(&pool)
        .await?;

    let state = AppState {
        pool,
        jwt_secret: config.jwt_secret.clone(),
    };

    let cors = CorsLayer::new()
        .allow_methods(Any)
        .allow_headers(Any)
        .allow_origin(Any);

    let app = Router::new()
        .merge(routes::auth_routes())
        .merge(routes::user_routes())
        .merge(routes::docs_routes())
        .layer(TraceLayer::new_for_http())
        .layer(CompressionLayer::new())
        .layer(cors)
        .with_state(state);

    let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
    tracing::info!("服务已启动: http://{}", addr);
    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;

    Ok(())
}

性能基准测试:Axum vs Gin

为了验证 Rust 后端的性能优势,我们使用 wrk 对同等复杂度的 CRUD 接口进行压测对比:

wrk -t12 -c400 -d30s http://localhost:3000/api/users?limit=20
指标 Rust/Axum Go/Gin 优势
吞吐量 (req/s)142,80062,4002.29x
P99 延迟3.2ms12.8ms4x
内存占用18 MB64 MB3.5x
CPU 占用(满载)0.8 核2.4 核3x
502/503 错误率0.00%0.03%-

压测环境:AWS c6i.xlarge (4vCPU/8GB),并发 400,持续 30s。两者均连接同一 PostgreSQL 16 实例。可以看到 Axum 在吞吐量和延迟维度均有显著优势,同时资源消耗更低。

Docker 化部署

利用 Rust 的静态编译能力,我们可以构建极致精简的 Docker 镜像:

FROM rust:1.85-slim-bookworm AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src ./src
COPY migrations ./migrations
RUN cargo build --release

FROM debian:bookworm-slim AS runtime
RUN apt-get update && apt-get install -y \
    ca-certificates libssl3 \
    && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/rust-axum-api /usr/local/bin/
COPY --from=builder /app/migrations /app/migrations
EXPOSE 3000
CMD ["rust-axum-api"]

最终镜像大小仅 23MB,对比 Go 单阶段构建的 ~15MB 和 JVM 应用的 ~200MB,Rust 在镜像大小和运行效率之间取得了极佳平衡。CI/CD 构建时间约 120 秒(首次),后续因 Cargo 缓存大幅缩短。

何时该选 Rust 后端?

Rust 不代表万能,以下是选型建议:

推荐使用 Rust/Axum 的场景:

  • 高并发 API 网关 / 反向代理(十万级 QPS)
  • 低延迟要求的关键路径服务(P99 小于 5ms)
  • 资源受限环境下的微服务(边缘计算、IoT)
  • 需要与 C/C++/Rust 原生库深度交互的 Web 层
  • 安全优先的应用(加密服务、认证授权中心)

可能需要慎重考虑的场景:

  • 快速原型验证 / MVP(编译时间较长,开发迭代慢于 Go/Node.js)
  • 团队无 Rust 经验且时间紧迫(学习曲线陡峭)
  • CRUD 为主的低复杂度管理后台(ROI 低)

总结

Rust 在后端开发领域的崛起不是偶然——它解决了 C/C++ 的内存安全问题、Go 的泛型局限性和 Java 的重量级运行时开销。tokio + axum + sqlx 这套技术栈已经在 Discord、Cloudflare、Fly.io 等公司的生产环境中证明了自身价值。

对于正在构建高性能 API 服务的团队来说,Rust 不再只是一个"有趣的系统语言",而是一个值得认真评估的工程选择。即使你暂时不打算将主力业务迁移到 Rust,在性能敏感的微服务中试点 Axum,往往能在成本和性能两方面获得立竿见影的回报。

完整的示例代码已开源:axum-api-starter,欢迎 Star。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部