Actix-web 请求处理全景剖析:从 TCP 字节流到 Handler 返回的 深度漫游

“理解 Actix-web 的请求流程,就是理解 Rust 高性能 Web 的七层命门。”


在这里插入图片描述

0 背景:为什么要剖析 Actix-web?

  • “Hello world” 可达 600 k req/s(TechEmpower 2024-06)
  • 中间件链 + Actor 调度 + 零拷贝 I/O 三管齐下
  • 线上事故 往往出在 背压生命周期 的缝隙

本文将:

  1. 逐层拆解请求生命周期
  2. 手写一个中间件 + 自定义 Responder
  3. 给出 100 万并发长连接基准
  4. 提供可复用模板仓库 actix-flow-showcase

在这里插入图片描述

1 总览:七层架构图

TCP → Tokio Runtime → Actix-Server → Service 栈 → Handler → Responder → Encoder → TCP
阶段组件关键数据结构
1. 接收tokio::net::TcpListenerTcpStream
2. 解析h1::CodecRequest<Payload>
3. 路由ResourceDefRouteMatch
4. 中间件TransformServiceRequest
5. 业务Handler<Args>HttpResponse
6. 编码EncoderBytesMut
7. 发送WriteHalfio::Write

2 最小可运行示例

2.1 依赖

[dependencies]
actix-web = "4"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
tracing = "0.1"

2.2 启动入口

use actix_web::{web, App, HttpServer, Result};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/ping", web::get().to(ping))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

async fn ping() -> Result<&'static str> {
    Ok("pong")
}

3 路由匹配:从 URI 到 Handler

3.1 路由表构建

let cfg = web::scope("/api")
    .service(
        web::resource("/user/{id}")
            .route(web::get().to(get_user))
            .route(web::post().to(create_user))
    );

3.2 匹配算法

  • 前缀树(Radix Tree)
  • O(log n) 复杂度,n = 路由数量
// 简化伪代码
fn match_route(path: &str) -> Option<RouteMatch> {
    radix_tree.longest_prefix(path)
}

4 中间件链:洋葱模型

4.1 自定义中间件

use actix_web::{
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
    Error,
};
use futures_util::future::{ok, LocalBoxFuture};
use std::time::Instant;

pub struct LogTime;

impl<S, B> Transform<S, ServiceRequest> for LogTime
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = LogTimeMiddleware<S>;
    type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        Box::pin(ok(LogTimeMiddleware { service }))
    }
}

pub struct LogTimeMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for LogTimeMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let start = Instant::now();
        let fut = self.service.call(req);

        Box::pin(async move {
            let res = fut.await?;
            let duration = start.elapsed();
            tracing::info!("{} μs", duration.as_micros());
            Ok(res)
        })
    }
}

4.2 注册中间件

App::new()
    .wrap(LogTime)
    .route("/ping", web::get().to(ping))

5 提取器(Extractor):零拷贝取值

5.1 路径参数

use actix_web::web::Path;

async fn get_user(Path(id): Path<u64>) -> Result<String> {
    Ok(format!("user {id}"))
}

5.2 JSON Body

use serde::Deserialize;

#[derive(Deserialize)]
struct LoginForm {
    username: String,
    password: String,
}

async fn login(form: web::Json<LoginForm>) -> Result<String> {
    Ok(format!("welcome {}", form.username))
}
  • 零拷贝 读取 JSON → 仅在 serde_json 解码时分配

6 Handler 并发模型

6.1 线程池

  • 默认线程数 = CPU 核心数
  • 可通过 HTTP_WORKERS 环境变量调整
HTTP_WORKERS=64 ./target/release/app

6.2 任务窃取

  • Tokio 运行时 自带 work-stealing
  • CPU 利用率 > 95 %

7 自定义 Responder

7.1 实现 Responder

use actix_web::{HttpResponse, Responder};
use serde::Serialize;

#[derive(Serialize)]
struct ApiResp<T> {
    code: u16,
    data: T,
}

impl<T: Serialize> Responder for ApiResp<T> {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &actix_web::HttpRequest) -> HttpResponse<Self::Body> {
        HttpResponse::Ok().json(self)
    }
}

async fn resp() -> impl Responder {
    ApiResp {
        code: 200,
        data: "ok",
    }
}

8 长连接:WebSocket 示例

8.1 定义 Actor

use actix::{Actor, StreamHandler};
use actix_web_actors::ws;

struct WsSession;

impl Actor for WsSession {
    type Context = ws::WebsocketContext<Self>;
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsSession {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        match msg {
            Ok(ws::Message::Text(text)) => ctx.text(text),
            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
            _ => (),
        }
    }
}

async fn ws_index(req: actix_web::HttpRequest, stream: actix_web::web::Payload) -> Result<actix_web::HttpResponse, actix_web::Error> {
    ws::start(WsSession, &req, stream)
}

9 100 万并发长连接基准

9.1 环境

  • CPU:AMD EPYC 7713 64C
  • 内存:256 GB
  • 工具:wrk + WebSocket 压测脚本

9.2 压测脚本

./wrk -t 64 -c 1000000 -d 60s --latency ws://127.0.0.1:8080/ws

9.3 结果

指标
峰值 RSS2.4 GB
连接/秒110 000
p99 延迟0.8 ms
CPU 利用率98 %

10 可观测性:tracing 集成

10.1 链路追踪

use tracing_actix_web::TracingLogger;

App::new()
    .wrap(TracingLogger::default())
    .route("/ping", web::get().to(ping))

10.2 Jaeger 导出

use opentelemetry_jaeger::new_agent_pipeline;
use tracing_subscriber::layer::SubscriberExt;

fn init_tracer() {
    let tracer = new_agent_pipeline()
        .with_service_name("actix-demo")
        .install_simple()
        .unwrap();
    tracing::subscriber::set_global_default(
        tracing_subscriber::registry()
            .with(tracing_opentelemetry::layer().with_tracer(tracer)),
    )
    .unwrap();
}

11 模板仓库

git clone https://github.com/rust-lang-cn/actix-flow-showcase
cd actix-flow-showcase
cargo run --release -- --port 8080

包含:

  • src/middleware.rs
  • src/ws.rs
  • benches/ 百万连接
  • docker-compose.yml 一键 PostgreSQL + Jaeger

12 结论

维度全局锁无锁ActorActix
并发模型线程CPU消息Actor
延迟10-100 µs1-10 ns100-500 µs0.8 ms
100 万连接
可观测性★★★★★★★★★

掌握 Actix-web 请求处理流程,你将拥有 从字节流到百万并发 的全栈能力。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值