20 KiB
raisfast Serverless 部署适配方案
版本:1.0 · 日期:2026-05-08
目标:一套代码同时支持传统服务器和 Serverless 部署, 通过
serverlessfeature flag 零改动切换运行模式。
1. 产品定位
1.1 市场空白
| 竞品 | 语言 | Serverless | 冷启动 | 单实例 RPS | 插件系统 |
|---|---|---|---|---|---|
| Strapi | Node.js | Vercel 有限 | ~500ms | ~2K | JS only |
| Payload | Node.js | Vercel only | ~500ms | ~2K | JS only |
| Directus | Node.js | 不支持 | ~800ms | ~1.5K | JS only |
| Ghost | Node.js | 不支持 | ~600ms | ~1K | 闭源 |
| WordPress | PHP | 不支持 | ~1s | ~500 | PHP only |
| raisfast | Rust | 全平台 | <5ms | 50K+ | WASM/JS/Lua |
核心差异化:Rust 性能 + Serverless 部署 + 多语言插件 + 多数据库 = 市场唯一。
1.2 支持的部署目标
| 平台 | 运行时 | 数据库 | 存储 | 冷启动 |
|---|---|---|---|---|
| AWS Lambda | provided (AL2) |
RDS / Aurora | S3 | <5ms |
| Vercel Edge | wasm32 |
Vercel Postgres | R2 / S3 | <5ms |
| Cloudflare Workers | wasm32 |
D1 / Hyperdrive | R2 | <5ms |
| Netlify Functions | provided |
Neon / PlanetScale | S3 | <10ms |
| Fly.io / Railway | Docker | 内置 PostgreSQL | Volume | <5ms |
| 传统 VPS / Docker | 原生 | SQLite / PG / MySQL | 本地 / S3 | N/A |
2. 架构总览
2.1 双模式架构
┌─────────────────────────────────────────────────────────┐
│ 业务逻辑层(共享) │
│ models/ · services/ · handlers/ · content_type/ │
│ repositories/ · middleware/ · errors/ · oauth/ │
├─────────────────────────────────────────────────────────┤
│ IO 抽象层(trait) │
│ Storage · CacheStore · SearchEngine · RateLimitStore │
│ EventBus · JobQueue · EmailSender · SmsSender │
├──────────────────┬──────────────────────────────────────┤
│ 传统模式(默认) │ Serverless 模式 │
├──────────────────┼──────────────────────────────────────┤
│ TcpListener │ Lambda handler / Vercel / Workers │
│ LocalStorage │ S3Storage / R2Storage │
│ MemoryCache │ RedisCache / D1Cache │
│ MemoryRateLimit │ RedisRateLimit │
│ Tantivy (disk) │ Tantivy (RAM) / Meilisearch │
│ tokio::spawn ×10 │ 请求内同步执行 │
│ broadcast EventBus│ 同步 fan-out / 外部队列 │
│ 文件日志 │ stdout JSON only │
│ plugin 磁盘加载 │ 编译时嵌入 / DB 加载 │
│ ServeDir │ CDN / S3 presigned URL │
└──────────────────┴──────────────────────────────────────┘
2.2 编译切换
# 传统服务器(默认)
cargo build
# Serverless 模式
cargo build --features serverless --no-default-features
# Serverless + PostgreSQL + S3
cargo build --features "serverless db-postgres storage-s3" --no-default-features
3. 改造清单
3.1 Feature Flag 设计
[features]
default = ["db-sqlite", "search-tantivy", "plugin-js", "plugin-lua", "openapi"]
serverless = [] # 启用 serverless 运行模式
# IO 后端(可独立选择)
storage-s3 = ["aws-sdk-s3", "aws-config"]
cache-redis = ["redis"] # 新增
rate-limit-redis = ["redis"] # 新增
search-external = [] # 新增:外部搜索服务(Meilisearch 等)
serverless feature 本身不引入新依赖,只改变运行时行为:
- 禁用所有
tokio::spawn后台任务 - 事件处理从异步订阅改为同步 inline
- 日志只输出 stdout
- Worker/Cron 改为请求内单次触发
3.2 改造项详细清单
按依赖关系排序,前面的项是后续项的前置条件。
3.2.1 运行模式开关
文件: src/config/app.rs
新增字段:
pub serverless: bool, // 读 SERVERLESS 环境变量,默认 false
效果:所有需要条件行为的代码通过 config.serverless 判断,
而非到处写 #[cfg(feature = "serverless")]。编译时 feature 控制依赖,
运行时 config 控制行为。
3.2.2 HTTP 入口适配器
现状: src/server.rs 使用 TcpListener::bind + axum::serve。
改造: 将 Router 构建与服务器启动分离。
src/server.rs (当前):
start() → TcpListener → axum::serve(router)
改造后:
build_app() → Router ← 共享,不变
start_standalone() ← 传统模式
start_serverless() ← 新增:返回 Router 或 handler
新增文件: src/serverless.rs
| 平台 | 入口 | 说明 |
|---|---|---|
| AWS Lambda | lambda_http::run(router.into_service()) |
使用 aws-lambda-rust-runtime |
| Vercel | #[vercel_runtime] handler |
使用 vercel_runtime crate |
| Cloudflare Workers | workers-rs entry |
使用 workers crate |
| 通用 | 导出 make_router() 函数 |
任何平台调用即可 |
关键:axum Router 是纯数据结构,与运行时无关。 只需将 Router 转为各平台的 handler 类型。
3.2.3 后台任务同步化
现状: 8-10 个 tokio::spawn + loop {} 死循环。
| 任务 | 文件 | Serverless 方案 |
|---|---|---|
| 事件→插件 Hook | server.rs:1365 |
emit_with_aspects() 已同步执行 Hook,改 EventBus::emit() 也同步调用插件 |
| 事件→审计日志 | server.rs:1418 |
同步写入 audit_log 表(INSERT 很快) |
| 事件→Webhook 投递 | server.rs:1574 |
同步 HTTP POST + 超时 5s,失败记入 jobs 表 |
| 事件→Job 入队 | worker/enqueuer.rs:24 |
同步入队(INSERT 很快) |
| Cron 调度器 | worker/scheduler.rs:353 |
每次请求检查 + 外部 cron trigger |
| Worker 执行器 | worker/runner.rs:46 |
请求内单次 dequeue + execute |
| Rate-limit 清理 | server.rs:1155 |
在 check() 内惰性清理,不启动后台任务 |
| 日志清理 | main.rs:37 |
不需要(serverless 无文件日志) |
| 插件文件监视 | plugins.rs:350 |
不启动(serverless 禁用 hot-reload) |
核心改造: EventBus 新增 emit_sync() 方法,在 serverless 模式下
直接调用所有订阅者(审计、webhook、入队),不经过 broadcast channel。
传统模式: emit() → broadcast channel → 各 spawn 的 subscriber
Serverless: emit_sync() → 直接调用 audit_write() + webhook_deliver() + job_enqueue()
3.2.4 文件系统消除
原则: 所有文件 IO 走已有的 Storage trait,不直接调用 std::fs。
| 当前位置 | 用途 | 改造方案 |
|---|---|---|
storage/local.rs |
上传文件 | 已有 S3Storage,serverless 默认用 S3 |
worker/handlers/thumbnail.rs:70 |
缩略图 | 改调 Storage::put() |
worker/handlers/sitemap.rs:80 |
sitemap.xml | 改调 Storage::put() |
content_type/schema.rs:850-855 |
schema TOML | 改为 DB 存储或编译时嵌入 |
content_type/schema.rs:1343 |
迁移 SQL 文件 | 改为 DB 存储或编译时嵌入 |
search/tantivy.rs:35 |
搜索索引目录 | 改为 Index::create_in_ram |
logging.rs:62,102 |
日志文件 | serverless 模式只输出 stdout |
plugins.rs:442+ |
插件磁盘加载 | 编译时嵌入或从 DB/Storage 加载 |
server.rs:1081 |
ServeDir 静态文件 | 改为 CDN / S3 presigned URL 重定向 |
cli/server_cmd.rs:18 |
PID 文件 | serverless 不写 PID |
3.2.5 日志系统
现状: src/logging.rs — DailyRollingWriter 写 JSON 日志到 ./logs/。
改造:
// logging.rs
if config.serverless {
// 只初始化 stdout subscriber,不创建文件 appender
init_stdout_only();
} else {
// 当前逻辑:stdout + 文件
init_with_file_logging();
}
Serverless 平台自动捕获 stdout:
- AWS Lambda → CloudWatch Logs
- Vercel → Vercel Logs
- Cloudflare Workers → Workers Logs
3.2.6 搜索引擎
现状: search-tantivy feature 使用本地文件系统索引。
改造方案:
| 方案 | 适用场景 | 工作量 |
|---|---|---|
Index::create_in_ram |
小型站点(<10万文档) | 低 |
| Meilisearch / OpenSearch | 大型站点 | 中 |
| Cloudflare Workers AI | Workers 部署 | 中 |
新增 search-external feature,通过 HTTP API 调用外部搜索服务。
SearchEngine trait 已有,只需新增实现。
3.2.7 缓存系统
现状: 只有 MemoryCache(moka)。
改造: 新增 cache-redis feature。
// cache.rs 新增
#[cfg(feature = "cache-redis")]
pub struct RedisCache { /* redis::aio::Connection */ }
#[cfg(feature = "cache-redis")]
impl CacheStore for RedisCache {
async fn get(&self, key: &str) -> Option<String> { /* GET */ }
async fn set(&self, key: &str, value: &str, ttl: Option<Duration>) { /* SET EX */ }
// ...
}
CacheStore trait 不需要改,只新增实现。
3.2.8 限流系统
现状: 只有 MemoryStore(DashMap),跨进程不共享。
改造: 新增 rate-limit-redis feature。
// rate_limit.rs 新增
#[cfg(feature = "rate-limit-redis")]
pub struct RedisRateLimitStore { /* ... */ }
// 滑动窗口算法:SORTED SET + ZADD + ZCOUNT + ZREMRANGEBYSCORE
3.2.9 插件系统
现状: 从 {plugin_dir}/ 磁盘加载 + notify 文件监视热更新。
改造:
| 改造项 | 方案 |
|---|---|
| 插件源 | 编译时嵌入(include_bytes!)或从 DB/Storage 加载 |
| 热更新 | serverless 模式禁用 |
| WASM 编译缓存 | OnceLock 懒初始化,冷启动时编译一次 |
| JS/Lua VM | OnceLock 懒初始化 |
| 插件 metrics | 写入 DB 或丢弃 |
关键:冷启动时加载插件的开销。 WASM 编译约 50-100ms/module,JS/Lua VM 约 10-20ms。 插件数量 < 20 时,总开销 < 500ms,可接受。
3.2.10 静态资源服务
现状: ServeDir 直接从磁盘提供 /uploads/* 和 /static/*。
改造:
/uploads/{key} → 302 重定向到 Storage::url() (S3/R2 presigned URL)
/static/* → 编译时嵌入或 CDN
/admin/* → rust-embed(已实现)
3.2.11 Content Type Schema
现状: 从 {content_type_dir}/*.toml 文件读取 schema 定义。
改造: 新增 content_type_source 配置项:
# 传统模式
CONTENT_TYPE_SOURCE=dir
CONTENT_TYPE_DIR=./content_types
# Serverless 模式
CONTENT_TYPE_SOURCE=db # 从 content_types 表读取
需要新增 DB 表存储 schema 定义(TOML 文本)。 首次启动时自动导入已有的 TOML 文件到 DB。
3.3 改造项依赖关系
3.2.1 运行模式开关 (config.serverless)
│
├── 3.2.2 HTTP 入口适配器
│
├── 3.2.3 后台任务同步化
│ └── 依赖:EventBus 改造
│
├── 3.2.4 文件系统消除
│ ├── thumbnail/sitemap → Storage::put()
│ ├── 搜索索引 → in-ram
│ ├── content-type schema → DB
│ └── plugin → 编译时嵌入/DB
│
├── 3.2.5 日志系统 (stdout only)
│
├── 3.2.6 搜索引擎 (in-ram / external)
│
├── 3.2.7 缓存系统 (Redis)
│
├── 3.2.8 限流系统 (Redis)
│
├── 3.2.9 插件系统 (懒加载 / 禁用热更新)
│
├── 3.2.10 静态资源 (CDN/S3)
│
└── 3.2.11 Content Type Schema (DB)
4. 实施路线
Phase 1:核心跑通(3 天)
让 raisfast 在 AWS Lambda 上跑通一个完整请求。
| 步骤 | 内容 | 预计耗时 |
|---|---|---|
| 1 | serverless feature flag + config.serverless |
2h |
| 2 | 日志 stdout-only 模式 | 1h |
| 3 | 禁用所有 tokio::spawn 后台任务 |
2h |
| 4 | EventBus 同步模式 (emit_sync) |
4h |
| 5 | Worker/Cron 请求内单次触发 | 4h |
| 6 | thumbnail/sitemap 改走 Storage::put() | 2h |
| 7 | Tantivy in-ram 模式 | 2h |
| 8 | ServeDir → S3 redirect |
2h |
| 9 | AWS Lambda 适配器 + 端到端测试 | 4h |
里程碑:curl https://xxx.lambda-url.aws.com/api/v1/posts 返回数据。
Phase 2:状态外部化(3 天)
消除所有进程内状态依赖。
| 步骤 | 内容 | 预计耗时 |
|---|---|---|
| 1 | Redis CacheStore 实现 | 1d |
| 2 | Redis RateLimitStore 实现 | 1d |
| 3 | Content Type Schema DB 存储 | 0.5d |
| 4 | Plugin 编译时嵌入 | 0.5d |
里程碑:多实例并发请求,缓存/限流/配置一致。
Phase 3:多平台适配(3 天)
| 步骤 | 内容 | 预计耗时 |
|---|---|---|
| 1 | Vercel 适配器 | 1d |
| 2 | Cloudflare Workers 适配器 | 1d |
| 3 | Meilisearch SearchEngine 实现 | 0.5d |
| 4 | 文档 + 部署模板 (SAM / Serverless Framework) | 0.5d |
里程碑:一键部署到 AWS / Vercel / Cloudflare。
Phase 4:生产优化(持续)
- 冷启动优化:lazy init → eager preload
- 外部搜索:OpenSearch / Elasticsearch / Typesense
- 外部队列:SQS / EventBridge / Cloudflare Queues
- CDN 集成:CloudFront / Cloudflare CDN
- 监控:OpenTelemetry / Prometheus remote write
5. 技术风险与应对
| 风险 | 影响 | 应对 |
|---|---|---|
| 冷启动慢(插件编译) | >2s 时用户体验差 | 预编译 WASM 到 DB;保持实例温热(最低并发 1) |
| SQLite 不支持并发写入 | Lambda 多实例冲突 | Serverless 模式强制 PostgreSQL/D1 |
| 请求内同步 webhook 增加延迟 | 慢 webhook 拖慢请求 | 超时 5s + 异步入队失败重试 |
| Redis 额外依赖 | 部署复杂度增加 | 提供 MemoryCache 降级模式(单实例可用) |
| Tantivy in-ram 内存占用 | 大索引 OOM | 限制索引大小 / 使用外部搜索 |
| Content Type Schema 从 DB 加载变慢 | 冷启动 +1s | OnceLock 缓存 + 懒加载 |
6. 配置参考
6.1 传统服务器模式
DATABASE_URL=sqlite:./data/raisfast.db
STORAGE_DRIVER=local
UPLOAD_DIR=./uploads
LOG_DIR=./logs
WORKER_ENABLED=true
PLUGIN_DIR=./plugins
CONTENT_TYPE_DIR=./content_types
BUILTIN_TENANTABLE=false
6.2 AWS Lambda + PostgreSQL + S3
SERVERLESS=true
DATABASE_URL=postgres://user:pass@aurora-cluster.region.rds.amazonaws.com/raisfast
STORAGE_DRIVER=s3
S3_BUCKET=raisfast-media
AWS_REGION=us-east-1
REDIS_URL=redis://cache.xxxxxx.ng.0001.use1.cache.amazonaws.com:6379
SEARCH_DRIVER=tantivy # in-ram
WORKER_ENABLED=false # 请求内触发
PLUGIN_HOT_RELOAD=false
CONTENT_TYPE_SOURCE=db
LOG_DRIVER=stdout
6.3 Cloudflare Workers + D1 + R2
SERVERLESS=true
DATABASE_URL=d1://raisfast-db
STORAGE_DRIVER=r2
R2_BUCKET=raisfast-media
SEARCH_DRIVER=noop
WORKER_ENABLED=false
PLUGIN_HOT_RELOAD=false
CONTENT_TYPE_SOURCE=db
LOG_DRIVER=stdout
7. 新增 trait / 实现
7.1 EventBus 同步模式
impl EventBus {
/// Serverless 模式:同步调用所有订阅者
pub fn emit_sync(&self, event: Event, handlers: &[SyncEventHandler]) {
for handler in handlers {
let _ = handler(event.clone()); // 忽略错误,继续下一个
}
}
}
7.2 RedisCache(新增)
#[cfg(feature = "cache-redis")]
pub struct RedisCache {
client: redis::Client,
}
#[cfg(feature = "cache-redis")]
impl CacheStore for RedisCache {
async fn get(&self, key: &str) -> Option<String>;
async fn set(&self, key: &str, value: &str, ttl: Option<Duration>);
async fn delete(&self, key: &str);
async fn delete_prefix(&self, prefix: &str) -> u64;
}
7.3 RedisRateLimitStore(新增)
#[cfg(feature = "rate-limit-redis")]
pub struct RedisRateLimitStore {
client: redis::Client,
}
#[cfg(feature = "rate-limit-redis")]
impl RateLimitStore for RedisRateLimitStore {
async fn check(&self, key: &str, config: &RateLimitConfig) -> bool;
async fn cleanup_expired(&self, window_secs: u64);
}
7.4 ExternalSearchEngine(新增)
#[cfg(feature = "search-external")]
pub struct ExternalSearchEngine {
base_url: String,
api_key: String,
}
#[cfg(feature = "search-external")]
impl SearchEngine for ExternalSearchEngine {
// HTTP 调用 Meilisearch / OpenSearch / Elasticsearch
}
8. 预期性能
8.1 冷启动时间估算
| 阶段 | 耗时 | 说明 |
|---|---|---|
| 二进制加载 | <1ms | Rust 编译为原生码 |
| DB 连接池 | 5-20ms | PostgreSQL TCP 握手 |
| Schema 检查 | <1ms | _migrations 表已存在 → 跳过 |
| Options 加载 | 2-5ms | 首次 SELECT → 缓存 |
| Content Type 加载 | 5-10ms | 从 DB 读 TOML → 解析 |
| Plugin 加载 | 50-200ms | WASM 编译 + JS/Lua VM init |
| Tantivy 索引 | 50-500ms | 从 DB 重建 RAM 索引(视数据量) |
| 总计 | 100-750ms | 首次请求;后续请求 <1ms |
对比:
- Node.js CMS 冷启动:500ms-2s
- Go CMS 冷启动:50-200ms
- raisfast 冷启动:100-750ms(插件/索引加载是瓶颈)
8.2 热请求性能
| 指标 | 传统模式 | Serverless |
|---|---|---|
| API 响应 P50 | <1ms | <2ms(多一次 Redis/DB) |
| API 响应 P99 | <5ms | <10ms |
| 单实例 RPS | 50K+ | 受平台限制 |
9. 文件改动总览
新增文件
| 文件 | 说明 |
|---|---|
src/serverless.rs |
Serverless 入口适配器 |
src/cache/redis.rs |
Redis CacheStore 实现 |
src/middleware/rate_limit_redis.rs |
Redis RateLimitStore 实现 |
src/search/external.rs |
外部搜索服务实现 |
src/storage/r2.rs |
Cloudflare R2 Storage 实现 |
src/content_type/db_loader.rs |
Content Type 从 DB 加载 |
src/plugins/embedded.rs |
编译时插件嵌入 |
修改文件
| 文件 | 改动 |
|---|---|
Cargo.toml |
新增 serverless、cache-redis、rate-limit-redis、search-external features |
src/config/app.rs |
新增 serverless、log_driver、content_type_source 字段 |
src/logging.rs |
新增 stdout-only 模式 |
src/eventbus.rs |
新增 emit_sync() 方法 |
src/server.rs |
条件禁用 tokio::spawn,Router 与启动分离 |
src/lib.rs |
条件选择缓存/限流/存储后端 |
src/main.rs |
条件禁用日志清理 loop |
src/worker/handlers/thumbnail.rs |
改走 Storage::put() |
src/worker/handlers/sitemap.rs |
改走 Storage::put() |
src/search/tantivy.rs |
新增 in-ram 模式 |
src/plugins.rs |
条件禁用 hot-reload,新增嵌入式加载 |
src/content_type/schema.rs |
新增 DB 加载路径 |
src/storage.rs |
新增 R2 factory |
src/cache.rs |
新增 Redis 实现 |
src/middleware/rate_limit.rs |
新增 Redis 实现 |
不改动的文件
| 目录/文件 | 说明 |
|---|---|
src/models/ |
数据结构,纯逻辑 |
src/services/ |
业务逻辑,已通过 trait 隔离 IO |
src/handlers/ |
路由处理,只调 service + 返回 JSON |
src/repositories/ |
数据访问,已用 sqlx + dialect 抽象 |
src/errors/ |
错误定义,纯逻辑 |
src/oauth/ |
OAuth 流程,纯 HTTP |
src/db/dialect.rs |
SQL 方言,已适配多 DB |
src/macros.rs |
宏定义,纯逻辑 |
migrations/ |
Schema 文件,不变 |
frontend/admin/ |
Admin SPA,不变 |
10. 结论
可行性:✅ 完全可行
项目已具备良好抽象基础:
Storage/CacheStore/SearchEngine/RateLimitStore/JobQueuetrait 全部就位- Repository 层已通过
Pool类型别名 +dialect::translate()实现多 DB 适配 - 业务逻辑与 IO 层完全分离
风险:可控
核心风险只有冷启动延迟,通过以下手段可控:
- 保持温热实例(所有平台都支持)
- 预编译插件(消除 WASM 编译开销)
- 懒加载搜索索引(首次搜索时才构建)
投入产出
- 开发投入:~9 个工作日
- 市场价值:成为唯一支持 Serverless 的 Rust Headless CMS
- 用户价值:零运维 + 按需付费 + 自动扩缩容 + 全球边缘部署