震撼!通过用 Rust 重写 Traefik 网关,使路由传播速度提升 2000 倍

357 阅读6分钟

本文来自 rivet.gg/blog/2025-0…

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 的架构:

  1. Traefik:充当网关,代理客户端请求至正确的 Function、Actor、Container 或 API
  2. HTTP provider:Traefik 每 500 毫秒轮询一次的 HTTP 接口,返回“当前所有路由、中间件和服务”的 JSON 描述,并在内部缓存
  3. 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 架构图 & 配置示例

核心通过以下三类函数配置:

  1. routing_fn:根据 hostname、path 返回目标 endpoints
  2. middleware_fn:根据 endpoint 返回限流/重试/超时等中间件策略
  3. cert_resolver_fn:根据 hostname 返回 TLS 证书

示例伪代码(略,代码已在原文展示)。

请求生命周期如下:

  1. 接受 TCP 连接
  2. TLS 握手
  3. HTTP 请求解析
  4. 路由解析(缓存 or 调用 routing_fn)
  5. 中间件解析
  6. 限流 & 并发控制
  7. 代理请求(支持 WebSocket、请求重试)
  8. 转发目标响应回客户端

其中关键是第 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 个无状态服务
  • 冷启动优化:连接并行启动资源
  • 功能灵活性:未来可支持更多协议与边缘特性

有时候,唯一的办法就是自己动手,打造真正贴合场景的利器。