Rivet 是什么
Rivet 是一个开源、可自托管的无服务器(serverless)平台,支持 Rivet Functions、Rivet Actors(开源版 Cloudflare Durable Objects)和 Rivet Containers。我们将原先基于 Traefik 的网关重写为 Rust 实现——现称为 Rivet Guard,以更好地满足构建 serverless 平台的高可定制性需求。
什么是 Rivet Guard
Rivet Guard 是我们平台的网关服务,负责处理所有进入 Rivet 平台的流量。其核心职责包括:
- Rivet Function 路由:将特定路径和主机名请求转发至正确的 Rivet Function
- Rivet Actors 与 Rivet Containers 路由:将请求路由到指定的 Actor 或 Container
- Rivet API:将 API 请求转发到 Rivet API 服务器
Traefik 的问题
Traefik 是一款流行的反向代理,广泛用于动态负载均衡和路由配置。最初,我们选择它用于构建 Rivet 的 MVP,因为配置动态路由非常便捷,但我们一直视其为临时方案,直到能自己实现更成熟的网关为止。
Traefik HTTP Provider 架构
在 MVP 阶段,我们使用以下三部分构成基于 Traefik 的架构:
- Traefik:充当网关,代理客户端请求至正确的 Function、Actor、Container 或 API
- HTTP provider:Traefik 每 500 毫秒轮询一次的 HTTP 接口,返回“当前所有路由、中间件和服务”的 JSON 描述,并在内部缓存
- NATS 缓存清理 Pub/Sub:当路由更新时,通过一个 NATS topic 通知 HTTP 接口清理缓存
当请求进入 Traefik → HTTP provider 获取路由 → 返回 JSON → Traefik 解析配置→转发请求
Traefik 性能瓶颈
问题一:路由传播缓慢
为了保证资源一旦创建就马上可用,我们专门优化后端基础架构,但 Traefik 成了最大瓶颈。尽管理论配置是 500 ms 轮询 + 0.025 s 解析节流,实践中往往延迟 1–2 秒。因此我们不得不在调用前人为等待 2 秒才能确保路由生效——这让前端所有的优化都被浪费了。
问题二:HTTP Provider 配置巨大
Traefik 中每条路由至少包括三个配置块(route、service、自定义中间件),每条至少 1.3 KB JSON。当需要处理 100+ 或 800 个路由(多区域部署时)时,HTTP 返回体积达 3 MB 以上。我们发现当 payload 达到 ~14 MB 时 Traefik 会完全卡住,迫使我们不得不重新设计。
全面重构:Rivet Guard
我们重新设计的目标是:
- 极快路由传播:资源创建完成后立即可用
- 无限路由:随着 Developer 创建 Actor/Function 无上限
- 无状态架构:不依赖复杂的配置生成或物化
- 简洁架构:更少组件、高灵活性
核心思想:延迟解析 + 缓存
Rivet Guard 不是基于 Traefik 的配置模式,而是一个库式服务,可注入自定义路由处理逻辑。它按照请求到来时再调用用户实现的 routing_fn,异步返回目标 Endpoint 并缓存结果:
- 简化架构:从 Traefik + API + NATS → 单一服务
- 完全无状态:按需获取路由并缓存
- 无限扩展:无路由上限限制
- 简易调试:可利用现有监控组件
- 灵活定制:支持正则、动态查询等高级路由逻辑
- 代码配置:取代复杂的 Traefik 配置文件
Rivet Guard 架构图 & 配置示例
核心通过以下三类函数配置:
- routing_fn:根据 hostname、path 返回目标 endpoints
- middleware_fn:根据 endpoint 返回限流/重试/超时等中间件策略
- cert_resolver_fn:根据 hostname 返回 TLS 证书
示例伪代码(略,代码已在原文展示)。
请求生命周期如下:
- 接受 TCP 连接
- TLS 握手
- HTTP 请求解析
- 路由解析(缓存 or 调用 routing_fn)
- 中间件解析
- 限流 & 并发控制
- 代理请求(支持 WebSocket、请求重试)
- 转发目标响应回客户端
其中关键是第 4 和第 7 步。
从 2 s → 1 ms:2000× 提升
通过 routing_fn 即刻查询目标 endpoint(无轮询),底层使用 FoundationDB 的单键 GET 操作,延迟仅 0.1–1 毫秒,最坏情况 1 ms,大幅快过之前的 2 s。这是 2000× 的性能提升。
首次访问会存入缓存,后续请求几乎无延迟。
加速冷启动 & 并行连接
Kubernetes 启动容器需拉镜像、启动、健康检查等多个步骤,速度慢。而 Rivet 支持提前返回 Container/Actor URL,连接和启动并行进行:
- 并行: 容器启动 与 URL 返回+客户端连接 并行,节省 round-trip 时间
如对端无响应,Rivet Guard 会智能缓冲请求,待资源准备好后再发送。这使得冷启动和重启时,不会因网关超时产生 Bad Gateway 错误。
这种 buffering 模型借鉴了 FoundationDB 的升级策略:客户端重试直到连接成功,用户无感知。
缓存机制与自动失效
路由缓存按 Host + path prefix 缓存目标与中间件,不同过期策略。当连接目标失败(比如被拒绝),会清除缓存并重试。为避免新的容器绑定同端口冲突,Guard 在缓存过期后延迟释放端口。
为什么用 Rust 而不是 Go?
我们并非歧视 Go——它也完全胜任。然而我们选择 Rust 的主因:
- 全栈一致:Rivet 全部生态已用 Rust 编写,可复用监控/数据库工具。
- 基于 Hyper & Tungstenite:成熟且支持未来协议(QUIC 等)。
- 内存安全与正确性保障:Rust 降低空指针/非法状态等 bug 风险,关键路径更可靠。
- 无 GC 暂停:性能更可控,如网络高性能场景尤为关键。
其他替代方案对比
- Envoy:虽然支持 xDS,但功能太通用,复杂度和配置开销大。又要为 UDP 加入 Quilkin,整体复杂度高,不适合当下需求。
- Cloudflare Pingora:功能类似,但我们目标代码量小(rivet-guard-core ~1.8k LOC,逻辑实现在 861 LOC),Quarantine 多边场景更灵活、更易融合当前逻辑。
未来展望
构建好基础后,我们计划继续扩展功能:
- 网络唤醒:流量来访时动态唤醒容器
- WS/SSE 休眠保活:类似 Durable Objects,实现连接中继与后端休眠
- 基于请求的自动扩缩容:相比 HPA 更快速响应突发流量
- 流量分析
- Anycast 支持:打造自有边缘网络入口
- QUIC / HTTP/3 / WebTransport:实验性协议支持,将由 Guard 独立实现
总结
通过 Rivet Guard,我们实现了以下关键提升:
- 2000× 更快路由传播:从 2 s 降至 1 ms
- 无限扩展:告别 14 MB 配置限制
- 请求缓冲:优雅处理冷启动与失败重连
- 架构简化:由 3 个组件淘汰至 1 个无状态服务
- 冷启动优化:连接并行启动资源
- 功能灵活性:未来可支持更多协议与边缘特性
有时候,唯一的办法就是自己动手,打造真正贴合场景的利器。