Table of contents
Open Table of contents
全局图
客户端
│
│ TCP 字节流
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Linux 内核 │
│ 网卡 → 中断 → 协议栈 (IP → TCP) → socket 接收缓冲区 │
└──────────────────────────────┬──────────────────────────────────────┘
│ epoll 通知 fd 可读
▼
┌─────────────────────────────────────────────────────────────────────┐
│ tokio (异步运行时) │
│ mio 收到 epoll 事件 → 唤醒等待这个 socket 的 task │
└──────────────────────────────┬──────────────────────────────────────┘
│ TcpStream 可读
▼
┌─────────────────────────────────────────────────────────────────────┐
│ hyper (HTTP 协议实现) │
│ 读取字节流 → 解析 HTTP 行 + Header → 构造 Request<Body> │
└──────────────────────────────┬──────────────────────────────────────┘
│ Request<Body>
▼
┌─────────────────────────────────────────────────────────────────────┐
│ tower Layer 链 (中间件) │
│ │
│ TraceLayer → 创建 span,记录方法/路径/开始时间 │
│ TimeoutLayer → 启动 30s 超时计时器 │
│ CorsLayer → 检查 Origin,通过 │
│ CompressionLayer → 标记"响应时压缩" │
└──────────────────────────────┬──────────────────────────────────────┘
│ Request<Body>(原样传递)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ axum Router (路由匹配) │
│ │
│ POST /api/v1/players/42/items │
│ → 匹配路由 /api/v1/players/{player_id}/items │
│ → 找到 handler: create_item │
│ → 把 {player_id} = "42" 存入 request.extensions │
└──────────────────────────────┬──────────────────────────────────────┘
│ Request<Body> + 路由匹配结果
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Extractor 提取参数 │
│ │
│ State(state) ← extensions 中取 AppState(clone Arc) │
│ Path(player_id) ← extensions 中取路由参数,反序列化 "42" → u32 │
│ BearerToken(token) ← headers 中取 Authorization,剥离 "Bearer " │
│ Json(body) ← 读取整个 body,serde 反序列化为 CreateItem │
│ │
│ 任何一步失败 → Rejection → 直接返回 4xx 错误响应,不进入 handler │
└──────────────────────────────┬──────────────────────────────────────┘
│ 提取完毕的参数
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Handler (你的业务逻辑) │
│ │
│ async fn create_item( │
│ State(state): State<AppState>, │
│ Path(player_id): Path<u32>, │
│ BearerToken(token): BearerToken, │
│ Json(body): Json<CreateItem>, │
│ ) -> Result<Json<Item>, AppError> { │
│ │
│ ┌─ 1. 验证 token ─────────────────────────────────────────┐ │
│ │ 查 Redis 缓存: session:{token} 是否存在 │ │
│ │ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ Redis │ │ │
│ │ │ GET session:eyJhbG... │ │ │
│ │ │ → 命中: player_id = 42 ✓ │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ 2. 写入数据库 ─────────────────────────────────────────┐ │
│ │ INSERT INTO items (player_id, name, quantity) │ │
│ │ VALUES (42, 'AK-47', 1) RETURNING * │ │
│ │ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ PostgreSQL │ │ │
│ │ │ 连接池取一个连接 │ │ │
│ │ │ 执行 SQL │ │ │
│ │ │ 返回新 Item { id: 1001, ... } │ │ │
│ │ │ 连接归还连接池 │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ 3. 失效缓存 ───────────────────────────────────────────┐ │
│ │ DEL player:42:items(下次查询时重新加载) │ │
│ │ → Redis DEL │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ return Ok(Json(item)) │
│ } │
└──────────────────────────────┬──────────────────────────────────────┘
│ Result<Json<Item>, AppError>
▼
┌─────────────────────────────────────────────────────────────────────┐
│ IntoResponse (结果 → HTTP 响应) │
│ │
│ Ok(Json(item)) │
│ → StatusCode: 200 │
│ → Header: Content-Type: application/json │
│ → Body: {"id":1001,"name":"AK-47","quantity":1,"player_id":42} │
│ │
│ 如果是 Err(AppError): │
│ → tracing::error! 记录完整错误链到日志 │
│ → StatusCode: 500 / 404 / 400(取决于错误类型) │
│ → Body: {"error": "..."} │
└──────────────────────────────┬──────────────────────────────────────┘
│ Response<Body>
▼
┌─────────────────────────────────────────────────────────────────────┐
│ tower Layer 链 (中间件 — 响应方向,从内往外) │
│ │
│ CompressionLayer → Content-Encoding: gzip,压缩 body │
│ CorsLayer → 加 Access-Control-Allow-Origin 响应头 │
│ TimeoutLayer → 取消计时器(没超时) │
│ TraceLayer → 关闭 span,记录 status=200, latency=23ms │
│ → 可观测性: span 导出到 Jaeger/Tempo │
│ → 可观测性: 打印日志 │
│ → 可观测性: counter!("requests_total").increment │
└──────────────────────────────┬──────────────────────────────────────┘
│ Response<Body>(加了 header + 压缩)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ hyper (HTTP 序列化) │
│ │
│ Response<Body> │
│ → "HTTP/1.1 200 OK\r\n" │
│ → "content-type: application/json\r\n" │
│ → "content-encoding: gzip\r\n" │
│ → "access-control-allow-origin: *\r\n" │
│ → "\r\n" │
│ → [gzip 压缩后的 JSON 字节] │
└──────────────────────────────┬──────────────────────────────────────┘
│ 字节流
▼
┌─────────────────────────────────────────────────────────────────────┐
│ tokio → TCP write → 内核 → 网卡 → 网络 │
└──────────────────────────────┬──────────────────────────────────────┘
│
▼
客户端收到响应
各部分的职责
内核层
做了什么:
网卡收到以太网帧 → IP 层 → TCP 层 → 数据放入 socket 接收缓冲区
通过 epoll 通知 tokio "这个 fd 可读了"
你需要关心吗: 不需要
tokio
做了什么:
mio 监听 epoll 事件 → 唤醒对应的 task → poll task 的 Future
你需要关心吗: 不需要,写 async/await 就行
唯一需要注意: 不要在 async 函数里做 CPU 密集操作阻塞事件循环
hyper
做了什么:
请求方向: TCP 字节流 → 解析 HTTP 协议 → Request<Body>
响应方向: Response<Body> → 序列化 HTTP 协议 → TCP 字节流
管理: HTTP/1.1 keep-alive、HTTP/2 多路复用
你需要关心吗: 不需要,axum 封装好了
tower Layer 链
做了什么:
请求进来时(从外到内):
TraceLayer → 创建 tracing span(请求开始)
TimeoutLayer → 启动超时计时器
CorsLayer → 检查 CORS 头
CompressionLayer → 记录客户端支持的压缩算法
响应出去时(从内到外):
CompressionLayer → 压缩响应 body
CorsLayer → 添加 CORS 响应头
TimeoutLayer → 取消计时器
TraceLayer → 关闭 span,记录耗时和状态码
你需要关心吗: 需要,你决定叠加哪些 Layer
let app = Router::new()
.route(...)
.layer(TraceLayer::new_for_http())
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.layer(CompressionLayer::new())
.layer(CorsLayer::new().allow_any_origin());
axum Router
做了什么:
拿到 Request → 根据 method + path 匹配路由
找到对应的 handler → 把路由参数存入 request.extensions
你需要关心吗: 需要,你定义路由
Router::new()
.route("/api/v1/players/{player_id}/items", post(create_item))
Extractor
做了什么:
从 Request 的各个部分提取 handler 需要的参数
提取失败 → 直接返回错误响应,handler 不会被调用
提取来源:
Parts.extensions → State, Path
Parts.uri → Query
Parts.headers → HeaderMap, 自定义 BearerToken
Body → Json, String, Bytes
你需要关心吗: 需要,你选择用哪些 extractor 作为 handler 参数
Handler
做了什么:
你的业务逻辑。可能涉及:
- Redis: 查缓存、验证 session、限流计数
- PostgreSQL: CRUD 操作
- 外部 API 调用
- 计算逻辑
你需要关心吗: 这是你唯一真正需要写的部分
IntoResponse
做了什么:
把 handler 的返回值转成 HTTP Response
Ok(Json(item)) → 200 + application/json + 序列化
Err(AppError) → 500/404/400 + 错误信息
你需要关心吗: 需要为自定义错误类型实现 IntoResponse
可观测性(贯穿整个过程)
Traces (tracing + OpenTelemetry):
TraceLayer 创建顶层 span
handler 里的 #[instrument] 创建子 span
Redis/PostgreSQL 操作各自创建子 span
→ 整棵 span 树导出到 Jaeger/Tempo
Logs (tracing):
info!("creating item for player {}", player_id)
error!("database query failed: {}", err)
→ JSON 输出到 stdout → Promtail → Loki
Metrics (metrics crate):
counter!("http_requests_total", "method" => "POST", "path" => "/items")
histogram!("http_request_duration_seconds").record(elapsed)
gauge!("db_pool_active_connections").set(pool.size())
→ /metrics 端点 → Prometheus 抓取 → Grafana 看板
时间线视角
t=0.0ms 内核: TCP 数据到达 socket 缓冲区,epoll 通知
t=0.1ms tokio: 唤醒 task,开始 poll
t=0.2ms hyper: 开始解析 HTTP
t=0.5ms hyper: Request<Body> 构造完成
t=0.6ms TraceLayer: 创建 span {method=POST, path=/api/v1/players/42/items}
t=0.7ms TimeoutLayer: 启动 30s 计时器
t=0.8ms CorsLayer: 检查通过
t=0.9ms Router: 路由匹配成功
t=1.0ms Extractor: State 提取(clone Arc)
t=1.1ms Extractor: Path 提取("42" → u32)
t=1.2ms Extractor: BearerToken 提取
t=1.5ms Extractor: Json body 读取 + 反序列化
t=2.0ms Handler: 开始执行
t=2.5ms Handler → Redis: GET session:token (命中)
t=3.0ms Handler → PostgreSQL: 从连接池取连接
t=3.5ms Handler → PostgreSQL: INSERT 执行中...
t=8.0ms Handler → PostgreSQL: 返回新 Item,归还连接
t=8.5ms Handler → Redis: DEL 缓存
t=9.0ms Handler: return Ok(Json(item))
t=9.1ms IntoResponse: Json → Response<Body>
t=9.2ms CompressionLayer: gzip 压缩
t=9.3ms CorsLayer: 加响应头
t=9.4ms TimeoutLayer: 取消计时器
t=9.5ms TraceLayer: 关闭 span {status=200, latency=8.9ms}
t=9.6ms hyper: 序列化 HTTP 响应
t=10.0ms tokio: TCP write → 内核发出
一个简单请求,从到达到返回约 10ms,其中 5ms 花在数据库上。