# 请求生命周期 本文档描述一个 HTTP 请求从 TCP 接受到响应返回的完整生命周期,包括中间件链、路由、Handler、Service、数据库访问等各环节。 ## 1. 运行时模型 | 项目 | 值 | |------|-----| | Runtime | tokio multi-thread(`#[tokio::main]`,默认 CPU 核数 worker threads) | | HTTP 引擎 | hyper(通过 `axum::serve`) | | 连接池 | sqlx,默认 `max_connections=5`(`DB_POOL_SIZE` 环境变量可调) | | EventBus | `tokio::sync::broadcast`,容量 256 | | Worker 并发度 | 默认 2(`WORKER_CONCURRENCY` 环境变量可调) | ## 2. 完整流程图 以 `GET /api/v1/posts?q=rust&page=1` 为例: ``` ┌─────────────────────────────────────────────────────────────────┐ │ TCP ACCEPT(非阻塞,epoll/kqueue) │ │ tokio multi-threaded runtime 接受连接 │ │ hyper 为每个连接 spawn 一个独立 async task │ └──────────────────────┬──────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ HTTP/1.1 请求解析(hyper) │ └──────────────────────┬──────────────────────────────────────────┘ │ ┌────────────┴────────────┐ │ Axum Layer 链(外→内) │ │ 最后注册的最先执行 │ └────────────┬────────────┘ │ ┌──────────────────┼──────────────────────────────────┐ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ [L1] aop_http_layer │ │ │ │ AspectEngine::dispatch_http_before() │ │ │ │ 可能短路返回 JSON 响应 │ │ │ └──────────────────┬───────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ [L2] security_headers │ │ │ │ 注入 X-Content-Type-Options, X-Frame- │ │ │ │ Options, HSTS, Referrer-Policy 等 │ │ │ └──────────────────┬───────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ [L3] CorsLayer │ │ │ │ OPTIONS 预检 / Access-Control-* 头 │ │ │ └──────────────────┬───────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ [L4] TraceLayer(tower-http) │ │ │ │ 创建 tracing span "request" │ │ │ │ 日志:--> request start │ │ │ └──────────────────┬───────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ [L5] inject_request_id │ │ │ │ 生成 UUID v7 → X-Request-ID 响应头 │ │ │ └──────────────────┬───────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ [L6] track_metrics │ │ │ │ http_requests_total{active}++ │ │ │ │ 启动 wall-clock 计时器 │ │ │ └──────────────────┬───────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ [L7] locale_middleware │ │ │ │ ?lang= / Accept-Language → task_local │ │ │ └──────────────────┬───────────────────────┘ │ │ │ │ │ ┌───────────────┴───────────────┐ │ │ │ 进入 /api/v1 嵌套路由 │ │ │ └───────────────┬───────────────┘ │ │ │ │ │ ┌──────────────────┼──────────────────┐ │ │ │ ▼ │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ │ [IL1] RequestBodyLimitLayer │ │ │ │ │ │ 2MB body 大小限制 │ │ │ │ │ └──────────────┬───────────────┘ │ │ │ │ ▼ │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ │ [IL2] Extension(limiters) │ │ │ │ │ │ 注入 RateLimiterSet │ │ │ │ │ └──────────────┬───────────────┘ │ │ │ │ ▼ │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ │ [IL3] global_rate_limit │ │ │ │ │ │ IP 滑动窗口 60req/60s │ │ │ │ │ │ API Token 额外限流 │ │ │ │ │ └──────────────┬───────────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ │ 路由匹配(Router) │ │ │ │ │ │ GET /api/v1/posts │ │ │ │ │ │ → handlers::post::list │ │ │ │ │ └──────────────┬───────────────┘ │ │ │ │ │ │ │ └──│─────────────────│───────────────────│───────────┘ │ ▼ │ │ ┌──────────────────────────────┐ │ │ │ Handler 参数提取(顺序) │ │ │ │ │ │ │ │ 1. AuthUser │ │ │ │ ├ JWT Bearer 验签 │ │ │ │ ├ API Token 查 DB │ │ │ │ ├ 租户解析 │ │ │ │ └ 永不 reject │ │ │ │ │ │ │ │ 2. State(state) │ │ │ │ └ AppState clone(Arc) │ │ │ │ │ │ │ │ 3. Query(query) │ │ │ │ └ ?q=rust&page=1 │ │ │ └──────────────┬───────────────┘ │ │ ▼ │ │ ┌──────────────────────────────┐ │ │ │ Handler Body │ │ │ │ │ │ │ │ PaginationParams:: │ │ │ │ from_options(1, None) │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ post_service::list_posts() │ │ │ └──────────────┬───────────────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ Service Layer │ │ │ │ │ │ │ │ ┌─────────────────┐ │ │ │ │ │ Tantivy 全文搜索 │ │ │ │ │ │ → post_ids[] │ │ │ │ │ └────────┬────────┘ │ │ │ │ ▼ │ │ │ │ ┌─────────────────┐ │ │ │ │ │ sqlx 连接池 │ │ │ │ │ │ acquire() await │ │ │ │ │ └────────┬────────┘ │ │ │ │ ▼ │ │ │ │ ┌─────────────────┐ │ │ │ │ │ SQL JOIN 查询 │ │ │ │ │ │ posts + users │ │ │ │ │ │ + categories │ │ │ │ │ │ WHERE id IN(?) │ │ │ │ │ └────────┬────────┘ │ │ │ │ ▼ │ │ │ │ ┌─────────────────┐ │ │ │ │ │ SQL 批量查 tags │ │ │ │ │ │ 避免 N+1 │ │ │ │ │ └────────┬────────┘ │ │ │ │ ▼ │ │ │ │ 组装 PostResponse[] │ │ │ │ 返回 (items, total) │ │ │ └───────────┬───────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────┐ │ │ │ Ok(ApiResponse::success( │ │ │ │ PaginatedData { ... } │ │ │ │ )) │ │ │ └──────────────┬───────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────┐ │ │ │ IntoResponse → JSON 序列化 │ │ │ │ Content-Type: application/json│ │ │ └──────────────┬───────────────┘ │ │ │ │ └─────────────────│───────────────────┘ │ ┌──────────────┴──────────────┐ │ 响应原路返回(内→外) │ └──────────────┬──────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ [L7] locale: task_local scope 结束 │ │ [L6] metrics: 记录 histogram + status counter │ │ [L5] request_id: X-Request-ID header 已注入 │ │ [L4] TraceLayer: log "<-- request done" latency_ms │ │ [L3] CORS: 注入 Access-Control-* 头 │ │ [L2] security_headers: 注入安全头 │ │ [L1] aop_http: dispatch_http_after() │ └──────────────────────┬────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ hyper 写入 HTTP 响应到 TCP socket │ │ keep-alive 或 close(取决于 HTTP 版本) │ └──────────────────────────────────────────────────────┘ ``` ## 3. 写请求额外流程 以 `POST /api/v1/posts`(创建文章)为例,Service 层有额外步骤: ``` Handler Body │ ├── 参数校验(validation::validate) ← 内存,微秒级 │ ├── Plugin dispatch_filter(PostCreating) ← 插件可修改请求数据 │ ├── 业务逻辑 │ ├── slug 生成 + 唯一性检查 ← 1 次 DB 查询 │ ├── excerpt 自动提取 ← Markdown → plain text │ └── 全文索引更新(Tantivy) ← 写入内存索引 │ ├── Repository INSERT ← 1 次 DB 写入 │ ├── EventBus::emit(PostCreated) ← 同步 broadcast,微秒级 │ │ │ ├── → Event Subscriber(插件系统) ← 异步 tokio task │ ├── → Audit Subscriber(审计日志) ← 异步 tokio task │ └── → Webhook Subscriber(HTTP 投递) ← 异步 tokio task │ └── WorkerEnqueuer → jobs 表 ← 异步 │ └── 返回 ApiResponse ``` ## 4. 关键节点一览 | # | 节点 | 所在层 | 耗时量级 | 阻塞类型 | 说明 | |---|------|--------|---------|---------|------| | 1 | TCP accept | Runtime | μs | 非阻塞 | epoll/kqueue,无限流 | | 2 | HTTP 解析 | hyper | μs | 非阻塞 | HTTP/1.1 解帧 | | 3 | aop_http before | Middleware | μs~ms | 非阻塞 | Aspect 遍历,通常 0~5 个 | | 4 | CORS 检查 | Middleware | μs | 非阻塞 | preflight 直接返回 | | 5 | TraceLayer span | Middleware | μs | 非阻塞 | tracing span 创建 | | 6 | request_id 生成 | Middleware | μs | 非阻塞 | UUID v7 | | 7 | metrics 计数 | Middleware | μs | 非阻塞 | Prometheus atomic | | 8 | locale 检测 | Middleware | μs | 非阻塞 | Header 解析 | | 9 | RequestBodyLimit | Middleware | μs | 非阻塞 | 仅检查 Content-Length | | 10 | **global_rate_limit** | Middleware | **μs~ms** | 非阻塞 | DashMap 滑动窗口,但 IP 解析需读 Header | | 11 | **AuthUser 提取** | Extractor | **μs~ms** | 非阻塞/可能 DB | JWT 验签纯 CPU;API Token 需 1 次 DB 查询 | | 12 | State clone | Extractor | ns | 非阻塞 | Arc 浅拷贝 | | 13 | Query 解析 | Extractor | μs | 非阻塞 | URL decode + serde | | 14 | validation 校验 | Handler | μs | 非阻塞 | validator crate | | 15 | **Tantivy 全文搜索** | Service | **ms~10ms** | 非阻塞 | 内存索引,取决于索引大小 | | 16 | **sqlx 连接池 acquire** | Repository | **μs~5s** | 异步等待 | 池满时排队,acquire_timeout=5s | | 17 | **SQL 查询执行** | Repository | **ms~100ms** | spawn_blocking | SQLite 是 C 库,sqlx 用 blocking thread | | 18 | SQL 批量查 tags | Repository | ms | spawn_blocking | 避免 N+1,1 次批量查询 | | 19 | JSON 序列化 | IntoResponse | μs~ms | 非阻塞 | serde_json,取决于数据量 | | 20 | EventBus emit | Service | μs | 非阻塞 | broadcast send | | 21 | **媒体 URL 生成** | Handler | **ms*N** | 非阻塞 | S3 presigned URL 列表中逐个生成 | | 22 | **CachedPostRepository** | Repository | μs~ms | 非阻塞 | DashMap 缓存,命中时跳过 DB | ## 5. 潜在性能瓶颈 | # | 节点 | 风险等级 | 问题描述 | 优化建议 | |---|------|---------|---------|---------| | 1 | **SQLite 连接池** | 🔴 高 | 默认仅 5 连接,高并发下大量请求排队等 acquire,超过 5s 返回 500 | 调大 `DB_POOL_SIZE`;或切换 PostgreSQL | | 2 | **SQLite 写锁** | 🔴 高 | WAL 模式下读写并发,但写-写仍是串行的,`busy_timeout=5000` 可能不够 | 高写场景考虑 PostgreSQL;或批量写入 | | 3 | **媒体 URL 列表** | 🟡 中 | 列表 API 中每个 media 项逐个调用 `storage.url()` 生成 presigned URL,100 项 = 100 次 HMAC 计算 | 批量 URL 生成;或用固定 URL 模式 | | 4 | **内存分页** | 🟡 中 | tenant/rbac/plugin 的 list() 加载全量数据到内存再切片,数据量大时浪费 | 改为 SQL LIMIT/OFFSET | | 5 | **API Token 认证** | 🟡 中 | 每次请求查 DB 验证 token,高频场景增加 DB 压力 | 内存缓存 token→role 映射,TTL 过期 | | 6 | **AOP dispatch** | 🟢 低 | 每个请求遍历 aspect 列表,通常 ≤5 个,O(n) | 无需优化 | | 7 | **EventBus 慢消费者** | 🟢 低 | broadcast 容量 256,慢消费者会被 Lagged 丢事件 | 已有 warn 日志;必要时增大队列 | | 8 | **Tantivy 搜索** | 🟢 低 | 内存索引搜索速度快,但索引未持久化,重启需重建 | 定期 save-to-disk | | 9 | **无连接数限制** | 🟡 中 | `axum::serve` 未配置最大并发连接数,极端情况可能耗尽资源 | 加 `tower::limit::concurrency` | | 10 | **`into_make_service()`** | 🟡 中 | 未使用 `into_make_service_with_connect_info()`,无法直接获取客户端 IP | 改用 `with_connect_info::()` | ## 6. 后台任务 以下任务与 HTTP 服务器共享同一个 tokio runtime: | 任务 | 启动位置 | 并发模型 | 关停信号 | |------|---------|---------|---------| | 日志清理 | `main.rs` | 单 task,每 3600s | 无 | | Rate limiter 清理 | `server.rs` | 单 task,每 300s | `watch::Receiver` | | Event subscriber(插件) | `spawn_event_subscriber` | 单 task,broadcast recv | `watch::Receiver` | | Audit subscriber | `spawn_audit_subscriber` | 单 task,broadcast recv | `watch::Receiver` | | Webhook subscriber | `spawn_webhook_subscriber` | 单 recv,per-delivery spawn | `watch::Receiver` | | CronScheduler | `server.rs` | 单 task,轮询间隔可配 | 无 | | WorkerRunner | `server.rs` | `worker_concurrency` 并发 | 无 | | Plugin 文件监控 | `plugins.rs` | `notify::RecommendedWatcher` + debounced | graceful degradation | ## 7. 状态共享模型 ``` AppState (Clone = Arc 浅拷贝) ├── pool: SqlitePool ← Arc 内部,跨 task 共享 ├── config: Arc ← 不可变 ├── jwt_decoding_key ← 不可变 ├── plugins: Arc ← 内部有 RwLock + DashMap ├── eventbus: EventBus ← broadcast::Sender(Clone) ├── post_repo: Arc ← CachedPostRepository → DashMap 缓存 ├── *_repo: Arc ← 各 repo 内部持有 pool.clone() ├── search: Arc ← Tantivy 索引有 RwLock ├── content_type_registry: Arc ← DashMap + RwLock ├── aspect_engine: Arc ← DashMap + RwLock ├── rbac: Arc ← pool + 内存缓存 ├── storage: Arc ← S3 client 或 LocalFS ├── cms_cache: Arc ← 无锁缓存 └── ... ``` 所有共享状态通过 `Arc` 或内部 `DashMap`/`RwLock` 保证线程安全,无 `Mutex` 热点。 ## 8. Content Type 动态 CRUD 生命周期 Content Type 路由基于 TOML 定义的 Schema 动态生成 CRUD API,无需手写 Handler。 ### 8.1 路由注册 启动时 `register_content_routes()` 为每个 ContentTypeSchema 注册固定 axum 路由: ``` /cms/{plural} GET → list /cms/{plural} POST → create /cms/{plural}/{id_or_slug} GET → get /cms/{plural}/{id} PUT → update /cms/{plural}/{id} DELETE → delete /admin/cms/{plural} GET → admin list /admin/cms/{plural}/{id} GET → admin detail ``` 热加载的类型通过 catch-all 路由 `{*path}` 匹配,走 `dynamic_cms_handler`。 ### 8.2 列表请求流程 以 `GET /api/v1/cms/articles?page=1&include=author` 为例: ``` Middleware 链(同内置 API,见第 2 节) │ ▼ 路由匹配 → GET /cms/articles │ ▼ Handler(闭包) │ ├── 1. Schema 查找 │ registry.get("articles") ← ArcSwap 无锁读,O(1) │ → ContentTypeSchema (Arc) │ ├── 2. API 访问控制 │ check_api_access(ct.api.list.access, &auth) │ → Public / Member / Admin / None │ ├── 3. 查询构建 │ ├── build_rule_sql() ← 编译 API Rule 为 SQL WHERE │ │ (Rule 已在注册时预解析为 CachedRules) │ │ Auth 变量 @request.auth.id 实时替换 │ ├── 构造 ContentQuery │ │ page/page_size/sort/filters/status/search │ └── cms_cache 查找(DashMap TTL 缓存) │ 命中 → 直接返回 │ ├── 4. AOP before_read │ aspect_engine.dispatch_data_before_read() │ ├── 5. SQL 执行(ContentRepository::find) │ ├── 列名计算(首次 PRAGMA table_info,后续缓存) │ ├── WHERE 构建 │ │ ├── deleted_at IS NULL ← soft_deletable │ │ ├── tenant_id = ? ← 多租户隔离 │ │ ├── status = 'published' ← draft_publish 类型 │ │ ├── 字段筛选(?title=xxx) │ │ └── API Rule WHERE │ ├── ORDER BY(sort 参数或 default_sort) │ ├── LIMIT/OFFSET │ ├── [ASYNC] COUNT(*) 查询 ← 第 1 次 DB 查询 │ ├── [ASYNC] SELECT 查询 ← 第 2 次 DB 查询 │ └── row_to_value() 逐行转换 │ ├── 6. 关联展开(resolve_relations) │ ├── ManyToOne: 批量 SELECT ... WHERE id IN (?) │ ├── OneToMany: 批量 SELECT ... WHERE fk IN (?) │ └── ManyToMany: 批量 JOIN through 表 │ 每个 relation 类型 = 1 次额外 DB 查询(已避免 N+1) │ ├── 7. strip_meta() — 移除 __meta 字段 │ ├── 8. AOP after_read │ └── 9. 写入 cms_cache → 返回 PaginatedData ``` ### 8.3 创建请求流程 以 `POST /api/v1/cms/articles` 为例: ``` Handler │ ├── 1. Schema 查找 + API 访问控制 │ ├── 2. Plugin filter: ContentCreating ← 插件可修改 body │ ├── 3. AOP before_create │ ├── 4. ContentRepository::create │ ├── [ASYNC] BEGIN TRANSACTION │ ├── validate_create_tx() │ │ ├── 必填检查、类型检查、enum 校验 │ │ ├── pattern(正则)校验 │ │ └── [ASYNC] check_unique_fields() ← 逐字段 SELECT COUNT │ ├── UUID v7 生成 │ ├── inject_auto_fill() ← user_id/role/tenant_id │ ├── 动态 INSERT INTO table (cols) VALUES (...) │ ├── [ASYNC] COMMIT │ └── [ASYNC] find_by_id() 回读 │ ├── 5. cms_cache 失效(cms:articles:*) │ ├── 6. AOP after_create │ └── 7. Plugin action: ContentCreated ← 异步 fire-and-forget ``` ### 8.4 关键节点 | # | 节点 | 耗时 | 阻塞 | 说明 | |---|------|------|------|------| | C1 | Schema 查找 | μs | 非阻塞 | ArcSwap 无锁读 | | C2 | API Rule 编译 | μs | 非阻塞 | 预解析 + 实时变量替换 | | C3 | **cms_cache 查找** | μs | 非阻塞 | DashMap TTL 缓存,命中跳过全部 DB | | C4 | **COUNT + SELECT** | ms~100ms | spawn_blocking | 2 次 DB 查询 | | C5 | **关联展开** | ms | spawn_blocking | 每个 relation 类型 1 次批量查询 | | C6 | **unique 校验** | ms | spawn_blocking | 每个唯一字段 1 次 COUNT 查询 | | C7 | 动态 INSERT | ms | spawn_blocking | 含事务 begin/commit | | C8 | strip_meta | μs | 非阻塞 | 移除 __meta 字段 | ### 8.5 Content Type 性能瓶颈 | # | 瓶颈 | 风险 | 优化建议 | |---|------|------|---------| | C1 | **COUNT + SELECT 双查询** | 🟡 中 | 用 `COUNT(*) OVER()` 窗口函数合并为单查询 | | C2 | **API Rule 每请求编译** | 🟡 中 | 编译结果缓存(键 = rule hash + auth id) | | C3 | **cms_cache 无大小上限** | 🟡 中 | 加 LRU 淘汰或 max capacity | | C4 | **unique 校验串行** | 🟢 低 | 合并为单条 `SELECT ... OR ...` 查询 | ## 9. 插件动态路由生命周期 插件路由通过 `.fallback(handle_plugin_route)` 注册,是最后一个路由匹配点。 ### 9.1 路由匹配机制 ``` 所有 axum 路由不匹配 │ ▼ .fallback(handle_plugin_route) ← 兜底处理器 │ ├── 提取 path + method + headers ├── [ASYNC] 读取 body(上限 1MB) │ ▼ PluginManager::dispatch_route() │ ├── [ASYNC] 获取 plugins RwLock 读锁 │ ├── 遍历所有已加载插件 ← O(N*M) 线性扫描 │ │ │ ├── 检查插件是否 enabled │ ├── 遍历 manifest.routes[] │ │ └── path_matches_route() │ │ 逐段比较,:param 作为通配符 │ │ 例:/products/:id 匹配 /products/42 │ │ │ └── 找到匹配 → check_api_access() │ ▼ call_plugin_json() — 按引擎类型分发 ``` ### 9.2 插件执行流程 以 `GET /api/v1/plugins/ecommerce/products` 匹配到 JS 插件为例: ``` call_plugin_json() │ ▼ ┌─────────────────────────────────────────────────────┐ │ JS 引擎(rquickjs) │ │ │ │ ├── 从实例池 round-robin 获取 JsInstance │ │ │ (池大小 = 创建时固定) │ │ │ │ │ ├── ctx.with(|ctx| { ... }) │ │ │ ├── 查找 Plugin[handler] 函数 │ │ │ ├── 传入 input JSON │ │ │ │ {path, method, body, headers, params} │ │ │ ├── [SYNC] 执行 JS 函数 │ │ │ │ 插件可调用宿主函数: │ │ │ │ ├── vfsRead/vfsWrite/vfsDelete │ │ │ │ ├── dbQuery/dbExecute │ │ │ │ ├── httpGet/httpPost │ │ │ │ ├── getConfig/getPost │ │ │ │ └── log/setData │ │ │ │ (宿主函数通过 HostContext 执行) │ │ │ │ │ │ │ ├── 中断检查(wall-clock timeout) │ │ │ └── 返回 JSON 结果 │ │ │ │ │ └── 清理中断 handler │ └─────────────────────────────────────────────────────┘ ``` **WASM 引擎**有所不同:通过 `block_in_place` 在 tokio blocking thread 上同步执行,有 fuel 限制(默认 10M)+ wall-clock timeout。 **Lua 引擎**:指令计数 hook(每 1000 指令检查一次),超出配额终止。 ### 9.3 宿主函数调用 插件执行期间可通过宿主函数访问系统资源: ``` 插件代码调用 vfsRead("data.json") │ ▼ js_host::vfs_read() / lua_host::vfs_read() │ ├── VFS 沙箱检查 │ ├── 路径转义防护(拒绝 ..) │ ├── 权限检查(read/write/read-write) │ └── 大小限制(单文件 + 总配额) │ └── [SYNC] std::fs::read() ← 在 block_in_place 中执行 ``` ``` 插件代码调用 dbQuery("SELECT * FROM tags") │ ▼ host_common::db_query() │ ├── 表权限检查(permissions.database 白名单) ├── 受保护表检查(users/roles 等禁止访问) ├── SQL 方言转换 └── [SYNC] tokio::task::block_in_place(|| { handle.block_on(sqlx::query(...).fetch_all(&pool)) }) ``` ### 9.4 关键节点 | # | 节点 | 耗时 | 阻塞 | 说明 | |---|------|------|------|------| | P1 | **body 全量读取** | μs~ms | 异步 | 即使 GET 请求也读取 body,上限 1MB | | P2 | **插件线性扫描** | μs~ms | 非阻塞 | O(N*M),N=插件数,M=路由数/插件 | | P3 | RwLock 读锁获取 | μs | 非阻塞 | 阻塞插件热加载 | | P4 | **JS 执行** | ms~s | ctx.with | 受 timeout 保护 | | P5 | **WASM 执行** | ms~s | block_in_place | fuel + timeout 双重保护 | | P6 | **Lua 执行** | ms~s | 同步 | 指令计数 hook | | P7 | **VFS 文件操作** | μs~ms | block_in_place | std::fs 同步 IO | | P8 | **DB 查询(宿主)** | ms | block_in_place | sqlx 连接池 + blocking | | P9 | HTTP 调用(宿主) | ms~s | 异步 | reqwest async | ### 9.5 插件路由性能瓶颈 | # | 瓶颈 | 风险 | 优化建议 | |---|------|------|---------| | P1 | **404 也走插件扫描** | 🔴 高 | 所有未匹配路径都读取 body + 遍历插件,大量 404 时浪费严重 | | P2 | **O(N*M) 路由匹配** | 🟡 中 | 构建路由前缀树(trie)替代线性扫描 | | P3 | **GET 请求读 body** | 🟡 中 | GET/HEAD/DELETE 跳过 body 读取 | | P4 | **无响应缓存** | 🟡 中 | 对幂等插件路由加缓存层 | | P5 | **WASM 实例池固定** | 🟢 低 | 运行时动态扩展实例池 | | P6 | **VFS 同步 IO** | 🟢 低 | 在 block_in_place 中已安全 | ## 10. 三种路由对比 | 维度 | 内置 API | Content Type | 插件路由 | |------|---------|-------------|---------| | 路由注册 | 编译时 axum route | 启动时动态 + catch-all | 运行时 fallback | | 路由匹配 | O(1) trie | O(1) trie | O(N*M) 线性 | | SQL 生成 | 静态 SQL(sqlx 宏检查) | 动态拼接(运行时) | 委托插件 | | 缓存 | CachedPostRepository | cms_cache (DashMap TTL) | 无 | | 校验 | validator crate | Schema 驱动(类型/必填/唯一/枚举/正则) | 委托插件 | | 关联展开 | 手写 JOIN | 自动 resolve_relations | 委托插件 | | 热加载 | 不支持 | 支持(ArcSwap registry) | 支持(文件监控 + RwLock) | | 核心瓶颈 | 连接池大小 | COUNT+SELECT 双查询 | 404 也走插件扫描 |