跳转至

设计动机

本文档介绍了我们在开发 sōzu 时应牢记的目标。 它将为功能决策、错误修复和重点领域提供信息。

起源

Sōzu 源于 Clever Cloud 在反向代理中处理频繁 配置更改的需求。由于该平台遵循不可变基础设施 原则,应用程序会频繁地从一个虚拟机移动到另一个虚拟机,并且 反向代理的路由配置必须更新。

像 HAProxy 这样的经典解决方案被设计为在每次配置 更改时重新启动其进程。这会导致一些问题:

  • 重新启动代理进程将导致 CPU 和 RAM 使用率的暂时飙升(资源使用量翻倍)
  • 处理挥之不去的连接:
  • 要么我们停止旧进程并丢失它仍在处理的连接
  • 要么我们保留旧进程直到连接完成,但如果我们定期重新启动,我们最终可能会有大量旧进程

主要目标

运行时配置更改

更改代理的配置应该是在运行时执行的常规任务。 我们不应该仅仅为了处理这个问题而需要重新启动整个进程。

这意味着配置管理必须支持动态更改,因此它必须 反映在我们用来驱动 sōzu 的工具中,并且我们需要提供良好的库 来处理该用例。

细粒度修改

我们可以推送小的修改,例如“删除此后端服务器”或 “添加此证书”,而不是重新加载整个状态(就像热重新加载配置文件一样)。 这为我们提供了有关运行时系统的更多信息, 例如知道我们何时可以实际删除后端服务器(一旦没有更多连接 正在使用它)。

有界资源使用

我们应该能够直接在 sōzu 的配置中修复资源使用(CPU、RAM、连接等)的限制。 我们不会在负载下无限期地增加资源使用,而是会立即拒绝新的流量。 这提供了背压,并允许客户端智能地进行重试,并为现有连接保持可预测的延迟和行为。

永不终止的软件

一旦你启动了 sōzu,它应该能够无限期地为流量提供服务。如果发生 导致失败的问题,它不应该导致整个代理崩溃,并且它应该 能够自动恢复处理流量。

这种方法的副作用是:二进制升级不应导致流量丢失。

这是当前多进程架构背后的主要驱动力。

次要目标

安全

由于 sōzu 很可能会看到来自大量客户端连接的流量,以及其 TLS 证书的 私钥,因此它应该足够安全以保护其内存。

为此,我们选择了 Rust,因为它具有内存安全功能。

可预测性

Sōzu 的内存使用和延迟应该具有非常可预测的行为。 任何不规则性都应被观察和消除。

这一立场推动了各种决策:

  • Rust 使我们能够避免垃圾收集暂停
  • 预分配和池化以避免热路径中的某些分配
  • 单线程、无共享架构以避免核心之间的同步

配置智能应该在工具中,而不是在 sōzu 内部

虽然 sōzu 能够处理细粒度的配置更改,但它的作用不是 连接到阳光下的每个配置管理工具(etcd、kubernetes 等)。 相反,我们应该提供编写连接 sōzu 和系统其余部分的工具的方法。

更进一步,处理配置更改的代码应该可以在 sōzu 之外重用。 目前,编写一个从文件加载配置状态的工具, 从 sōzu 获取当前状态,生成差异,然后为差异发送配置消息 到 sōzu 是相当容易的。

编写配置工具应该足够容易,并且配置协议应该可以 从任何语言访问。