系统过载时施加背压


转自Mechanical Sympathy

系统在持续负载下应如何响应?它是否应该继续接受请求,直到它的响应时间跟随致命的曲棍球棒,然后是崩溃?除非系统设计为处理到达的请求多于其处理能力的情况,否则通常会发生这种情况。如果我们看到请求的持续到达率超过了我们的系统能够处理的能力,那么就必须付出一些代价。让整个系统降级并不是我们想要为客户提供的理想服务。更好的方法是以我们系统的最大可能吞吐率处理事务,同时保持良好的响应时间,并拒绝超过此到达率的请求。

让我们将一个小型美术馆作为一个隐喻。在这个画廊中,典型的观众平均花费 20 分钟浏览,画廊最多可容纳 30 位观众。如果超过 30 位观众同时占据画廊,那么客户就会因为无法清楚地看到画作而变得不高兴。如果发生这种情况,他们不太可能购买或退货。为了让我们的观众满意,最好建议一些观众去几扇门下的咖啡馆,然后在画廊不那么忙的时候回来。这样,画廊里的观众就可以在没有其他观众的情况下看到所有的画作,同时那些我们无法容纳的人享用咖啡。如果我们应用利特尔定律我们不能让客户到达每小时超过 90 个,否则会超出最大容量。如果他们在 9:00-10:00 之间以每小时 100 人的速度到达,那么我相信路边的咖啡馆会感谢额外的 10 位顾客。

在我们的系统中,可用容量通常取决于我们的线程池大小和处理单个事务的时间。这些线程池通常以队列为前端,以处理高于我们最大到达率的流量突发。如果队列是无界的,并且我们的到达率持续高于最大容量,那么队列将不受限制地增长。随着队列的增长,它们越来越多地增加了超出可接受响应时间的延迟,最终它们将消耗所有内存,导致我们的系统出现故障。将溢出的请求发送到咖啡馆,同时仍以最大可能的速度为其他人提供服务不是更好吗?我们可以通过设计我们的系统来应用“背压”来做到这一点。

img
图1。

关注点分离鼓励在所有级别进行良好的系统设计。我喜欢分层设计,以便第三方网关与主要交易服务分开。这可以通过让网关只负责协议转换和边界安全来实现。典型的网关可以是运行Servlets的 Web 容器。网关接受客户请求,应用适当的安全性,并转换通道协议以转发到托管域模型的事务服务. 如果需要保留事务,事务服务可以使用持久存储。例如,聊天服务器域模型的状态可能不需要保存,而出于合规和业务原因,金融交易模型必须保存多年。

图 1. 上面是许多系统中典型请求流的简化视图。网关中的线程池接受用户请求并将它们转发给事务服务。假设我们有一个异步事务服务,前面有一个输入和输出队列,或类似的FIFO结构。如果我们希望系统满足响应时间服务质量(QoS)保证,那么我们需要考虑以下三个变量:

  1. 线程上单个事务所花费的时间
  2. 池中可以并行执行事务的线程数
  3. 设置最大可接受延迟的输入队列长度

最大延迟=(事务时间/线程数)*队列长度

队列长度=最大延迟/(事务时间/线程数)

通过允许队列不受限制,延迟将继续增加。因此,如果我们想设置最大响应时间,那么我们需要限制队列长度。

通过限制输入队列,我们阻止了接收网络数据包的线程,这将向上游施加背压。如果网络协议是 TCP,则通过填充网络缓冲区对发送方施加类似的背压。这个过程可以通过网关一直重复到客户那里。对于每项服务,我们都需要配置队列,以便它们在实现端到端客户体验所需的服务质量方面发挥作用。

我经常发现的最大胜利之一是缩短处理单个事务延迟所需的时间。这有助于最好和最坏的情况。

最坏

的情况假设队列是无限的,系统处于持续的重负载下。在内存耗尽之前,事情可能会以微妙的方式很快开始出错。当队列大于处理器缓存时,您认为会发生什么?消费者线程将在他们努力跟上的时候遭受缓存未命中,从而使问题更加复杂。这可能会导致系统很快陷入困境并最终崩溃。在 Linux 下,这尤其令人讨厌,因为malloc或其朋友之一会成功,因为 Linux 默认允许“过度提交”,然后在使用该内存时, OOM Killer将开始拍摄过程。当操作系统开始拍摄过程时,你就知道事情不会有好的结局!

那么同步设计呢?

你可能会说同步设计没有队列。好吧,不是那么明显。如果您有一个线程池,那么它将有一个锁或信号量等待队列来分配线程。如果您足够疯狂地为每个请求分配一个新线程,那么一旦您克服了创建线程的巨大成本,您的线程就会在运行队列中等待处理器执行。此外,这些队列涉及上下文切换和条件变量,这大大增加了成本. 你不能逃避排队,他们无处不在!最好接受它们并设计您的系统需要向其客户提供的服务质量。如果我们必须有队列,那就为它们设计,也许选择一些性能很好的无锁队列。

当我们需要支持像 REST 这样的同步协议时,使用背压(由我们在网关处的完整传入队列发出信号)发送有意义的“服务器繁忙”消息,例如 HTTP 503 状态代码。然后,客户可以将此解释为在路边的咖啡馆享用咖啡和蛋糕的时间。

需要注意的细微之处…

您需要考虑整个端到端服务。如果客户端从您的系统中使用数据的速度非常慢怎么办?它可能会在网关中绑定一个线程,使其停止工作。现在你有更少的线程在队列中工作,所以响应时间会增加。需要监控队列和线程,并且在超过阈值时需要采取适当的措施。例如,当队列已满 70% 时,是否应该发出警报以便进行调查?此外,还需要对交易时间进行抽样,以确保它们在预期范围内。

概括

如果我们不考虑我们的系统在重负载下的表现,那么它们很可能会严重退化,最坏的情况是崩溃。当它们以这种方式崩溃时,我们会发现是否有任何真正邪恶的数据损坏漏洞潜伏在那些黑暗的地方。应用背压是应对持续高负载的一种有效技术,这样可以在不降低已接受请求和事务的系统性能的情况下提供最大吞吐量。


文章作者: Kevin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kevin !
评论
  目录