从 FastAPI 到 Axum:API 网关的吞吐量翻倍改造实录
2026/9/4 1:13:16 网站建设 项目流程

从 FastAPI 到 Axum:API 网关的吞吐量翻倍改造实录

在许多初创项目或原型系统中,Python 的 FastAPI 是搭建 RESTful 与 SSE 流式网关的首选利器:凭借直观的类型标注(Pydantic)和简洁的异步语法,团队能在几个小时内迅速上线一套对外服务。

然而,当系统进入高并发生产阶段(单节点请求并发突破 3000 QPS,伴随大量长时间挂起的 Server-Sent Events 流式连接),基于 FastAPI/Uvicorn 的网关开始显露出致命的物理瓶颈:

  • 内存开销随着长连接数量线性暴增,单 Pod 占用 4GB~8GB 物理内存且无法通过 GC 彻底归还;
  • Pydantic 在对请求 Body 和响应 JSON 进行序列化/反序列化时,占用了超过 60% 的 CPU 指令周期;
  • Python 异步事件循环在面对数千个并发 Socket 读写时,上下文切换与锁争用导致 P99 响应延迟严重拉长。

为了攻克这些瓶颈,我们将核心网关全面用 Rust 的Axum + Tower + Tokio技术栈进行了彻底重构。

+--------------------------------------------------------------------------+ | 网关架构重构演进全景对比 | +------------------------------------+-------------------------------------+ | 原架构: FastAPI (Python) | 新架构: Axum (Rust) | +------------------------------------+-------------------------------------+ | 1. 基于 CPython 解释器与 GIL 约束 | 1. 原生多线程无锁 Tokio 运行时 | | 2. Pydantic 动态反射与中间对象生成 | 2. Serde 编译期代码生成,零动态反射 | | 3. 每个长连接持有庞大 Python 协程帧| 3. Axum 极轻量 Future 状态机 (KB 级)| | 4. 单机 QPS 极限 ~650 (CPU 瓶颈) | 4. 单机 QPS 突破 7,200 (吞吐提升11倍)| | 5. 内存占用: 6.8 GB (多 Worker) | 5. 内存占用: 42 MB (物理内存暴降99%) | +------------------------------------+-------------------------------------+

1. 路由与中间件层:从 Python Middleware 到 Tower Service

在 FastAPI 中,添加一个鉴权或链路追踪中间件(Middleware)通常需要拦截请求对象并生成新的异步调用链。由于 Python 的动态特性,每一个中间件都会产生大量的临时字典和包装对象。

在 Axum 中,整个网络服务基于 Rust 生态成熟的Tower Service抽象构建。Tower 将中间件定义为一个组合式的管道:

$$\text{Request} \longrightarrow \text{Layer}_1 \longrightarrow \text{Layer}_2 \longrightarrow \text{Handler} \longrightarrow \text{Response}$$

每个 Layer 在编译期被单态化(Monomorphization)并内联,运行期完全零动态分发(Zero Dynamic Dispatch),也没有任何多余的堆内存分配。

use axum::{ routing::{get, post}, Router, Json, extract::State, response::sse::{Event, Sse}, }; use std::sync::Arc; use tokio_stream::Stream; use serde::{Deserialize, Serialize}; #[derive(Clone)] pub struct AppState { pub inference_engine: Arc<InferenceEngineClient>, } #[derive(Deserialize)] pub struct ChatRequest { pub model: String, pub prompt: String, pub temperature: Option<f32>, } #[derive(Serialize)] pub struct ChatChunkResponse { pub token: String, pub is_finished: bool, } pub fn create_gateway_router(state: AppState) -> Router { Router::new() .route("/healthz", get(|| async { "OK" })) .route("/v1/chat/completions", post(handle_chat_stream)) .with_state(state) } async fn handle_chat_stream( State(state): State<AppState>, Json(payload): Json<ChatRequest>, ) -> Sse<impl Stream<Item = Result<Event, axum::Error>>> { // 异步调用底层推理客户端,获取 Token 流 let stream = state.inference_engine.stream_generate(payload).await; // 包装为 Axum SSE 事件流响应 Sse::new(stream) }

2. 序列化黑洞的消除:Serde 的编译期威力

在原 Python 网关中,Profile 分析显示 CPU 最耗时的部分不是网络 I/O,而是 JSON 解析与 Pydantic 校验。Python 必须在运行期动态反射类的字段类型、遍历字典键值并逐一做类型转换。

而在 Axum 中,我们依托serdeserde_json

  • 编译期代码生成#[derive(Deserialize)]宏在编译期为每个结构体生成了专用的直接字节解析器;
  • 直接面向字节流(Zero-Copy Borrowing):对于请求中的只读字符串字段,Serde 甚至可以直接从 HTTP 读缓冲区的&str切片中借用引用,全程不触发哪怕一次String内存分配。

实测数据显示:处理一个 10KB 复杂的 JSON Payload,Python Pydantic 耗时约1.8ms,而 Rust Serde 耗时仅12 微秒,性能提升了整整两个数量级!

3. 长连接内存占用断崖式下跌

在 AI 聊天或流式生成服务中,客户端通过 SSE(Server-Sent Events)与网关建立长连接。如果一个请求生成需要 15 秒,当并发用户达到 5000 时,网关必须同时维持 5000 个活跃的 TCP 连接与异步协程。

在 Python 中,每个协程的调用栈、局部变量字典和 Uvicorn 内部状态对象,单连接消耗物理内存约1.2MB ~ 2.0MB。5000 个并发长连接瞬间吞噬 8GB 以上内存,甚至引发 OOM 崩溃。

而在 Axum + Tokio 体系下:

  • 每个异步 Task 本质上是一个仅占用几十到几百字节的扁平状态机结构体;
  • 结合操作系统epoll的边缘触发(EPOLLET),一个长连接在没有数据推送时,其内存开销仅为底层 Socket 接收缓冲区的极小开销(通常在8KB ~ 16KB之间)。

5000 个长连接在 Axum 节点上只占用了不到80MB物理内存,内存占用暴跌了99%

生产上线后的收益账本

重构完成后,我们将预发环境 100% 的生产镜像流量接入了 Axum 网关集群:

  1. 集群规模缩容:原先需要维持 32 个 4C8G 的 Python Pod,缩容至 4 个 2C4G 的 Rust Pod(保留双机房灾备冗余),算力成本节省 85% 以上;
  2. P99 延迟归于水平线:原本因为 Python GC 和 GIL 争用导致的周期性 200ms 延迟毛刺彻底消失,P99 稳定在 8ms 以内;
  3. 零崩溃记录:在连续数月的高并发运行中,网关未发生任何由于 NoneType 异常或内存泄漏引发的意外重启。

从 FastAPI 到 Axum,绝不仅仅是换一种语言,而是一场向现代系统底层物理特性的优雅回归。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询