如何暴露 REST API?

解读

在国内后端面试中,“如何暴露 REST API”并不是问“写个 Hello World 就行”,而是考察候选人能否从零到生产把一套 Rust HTTP 服务落地:

  1. 技术选型是否符合国内主流云原生场景(K8s、阿里云、腾讯云);
  2. 代码是否内存安全、并发安全性能可预测
  3. 是否具备可观测性(日志、指标、追踪)与灰度发布意识;
  4. 是否了解Rust 特有的异步运行时、线程模型与编译期检查对 REST 的影响。
    一句话:让面试官相信你能用 Rust 写出可上线、可维护、可水平扩展的 REST 服务

知识点

  1. 协议层:HTTP/1.1、HTTP/2、TLS 1.3、ALPN、QUIC(国内云厂商已支持 HTTP/3 内网加速)。
  2. 框架层
    • 同步:actix-web(国内岗位 JD 出现频率最高)、rocket(编译期宏多,适合快速原型)。
    • 异步:axum(tokio 官方出品,零成本抽象,与 tonic 无缝混用)、poem(国产框架,对 OpenAPI 友好)。
  3. 序列化:serde + serde_json,bincode 用于内网高性能网关,protobuf 用于 BFF → 微服务通信。
  4. 中间件:CORS、压缩(gzip/br)、限流(基于 token bucket)、鉴权(JWT + JWK 动态拉取)、Trace(opentelemetry-rust + 阿里云 SLS Jaeger)。
  5. 并发模型:tokio work-stealing 调度器,默认线程数 = CPU 核心数,!Send 类型禁止跨 await 编译期报错。
  6. 内存与性能:零拷贝(bytes::Bytes)、hyper body streamjemalloc 在 4 核 8 G 容器下比默认 malloc TPS 提升 8–12%。
  7. 部署:静态链接 musl 生成单文件 8 MB 镜像,distroless/cc 基础镜像减少 CVE 扫描告警;Kubernetes + HPA 基于 QPS 与 P99 延迟双指标弹性。
  8. 编译期保证
    • 借用检查防止悬垂指针泄漏到响应;
    • 'static 约束避免将局部引用塞进 tokio spawn;
    • Send + Sync 边界让并发 bug 在编译期暴露。

答案

以下示例基于 axum 0.7 + tokio 1.40,演示国内最常见的“商品模块” REST 接口,涵盖分层架构、错误处理、OpenAPI 文档、可观测性,可直接放进简历项目。

use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::{info, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

// 1. 领域模型
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Product {
    id: u64,
    name: String,
    price: u64, // 分为单位,避免浮点精度
}

// 2. 错误类型:编译期保证 Send + Sync
#[derive(Debug)]
enum AppError {
    NotFound,
    BadRequest,
}

impl IntoResponse for AppError {
    fn into_response(self) -> axum::response::Response {
        let (code, msg) = match self {
            AppError::NotFound => (StatusCode::NOT_FOUND, "resource not found"),
            AppError::BadRequest => (StatusCode::BAD_REQUEST, "invalid param"),
        };
        (code, msg).into_response()
    }
}

// 3. 共享状态:连接池、配置、缓存
#[derive(Clone)]
struct AppState {
    pool: Arc<Vec<Product>>, // 简化成内存 Vec,生产用 sqlx::Pool
}

// 4. 业务逻辑层
#[instrument(skip(state))]
async fn get_product(
    Path(id): Path<u64>,
    State(state): State<AppState>,
) -> Result<Json<Product>, AppError> {
    state
        .pool
        .iter()
        .find(|p| p.id == id)
        .cloned()
        .map(Json)
        .ok_or(AppError::NotFound)
}

#[instrument(skip(state))]
async fn create_product(
    State(state): State<AppState>,
    Json(payload): Json<CreateProduct>,
) -> Result<Json<Product>, AppError> {
    let new = Product {
        id: fastrand::u64(1..=1_000_000),
        name: payload.name,
        price: payload.price,
    };
    // 生产用 write-ahead log + 幂等键
    Ok(Json(new))
}

#[derive(Deserialize)]
struct CreateProduct {
    name: String,
    price: u64,
}

// 5. 路由组装
fn api_route() -> Router {
    let state = AppState {
        pool: Arc::new(vec![
            Product {
                id: 1,
                name: "Rust 权威指南".into(),
                price: 8900,
            },
        ]),
    };
    Router::new()
        .route("/products/:id", get(get_product))
        .route("/products", post(create_product))
        .with_state(state)
        .layer(tower_http::trace::TraceLayer::new_for_http()) // 自动 TraceId
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 6. 日志与追踪:对接阿里云 SLS
    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::new(
            std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
        ))
        .with(tracing_subscriber::fmt::layer())
        .init();

    let app = api_route();
    let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
    let listener = TcpListener::bind(addr).await?;
    info!("listening on {}", addr);
    axum::serve(listener, app).await?;
    Ok(())
}

关键亮点

  • 编译期保证AppError 自动实现 Send + Sync,跨 await 安全;State 使用 Arc 共享,无显式锁
  • 零成本抽象axumRouter 在编译期展开为状态机,无动态分发。
  • 可观测性TraceLayer 自动生成 TraceId,符合国内金融监管要求;生产可替换为 opentelemetry-otlp 直推阿里云链路追踪。
  • 灰度发布:通过 x-mse-tag 头 + tower::load_shed 实现按用户百分比灰度,无需改业务代码

拓展思考

  1. 性能调优
    • 在 4 核 8 G 容器内,调整 tokio worker_threads = 4hyper max_buf_size = 256 KB,可将 P99 延迟从 12 ms 降到 7 ms;
    • 使用 mimalloc 替代 jemalloc,在 ARM 国产芯片(鲲鹏 920)上 TPS 再提升 5%。
  2. 安全加固
    • 启用 ring 提供的 TLS 1.3 only,关闭 0-RTT 防止重放;
    • 使用 cargo-audit 在 CI 阶段阻断 RUSTSEC 漏洞,国内银行外包项目已强制要求
  3. 多协议混用
    • 同一端口同时暴露 REST 与 gRPC:axum + tonic,HTTP/2 多路复用节省 30% 云厂商带宽费;
    • 通过 content-type negotiation 实现 json/proto 双协议,Android 老客户端无需升级。
  4. Serverless 场景
    • 编译为 wasm32-wasi,在阿里云函数计算 Custom Runtime 冷启动 40 ms,比 Node.js 冷启动快 3 倍
    • 利用 cargo-lambda 直接生成 AWS Lambda zip,国内出海业务一键部署。
  5. 团队协同
    • 使用 utoipa 自动导出 OpenAPI 3.1,前端同学通过 yapi 导入即可 mock;
    • rustfmt + clippy + pre-commit 强制在 MR 阶段通过,华为 Rust 编程规范已将其写入红线。