原作 | Armin Ronacher,2020.01.01
译者 | 豌豆花下猫@Python猫
异步(async)正风靡一时。异步Python、异步Rust、go、node、.NET,任选一个你最爱的语言生态,它都在使用着一些异步。异步这东西有多好,这在很大程度上取决于语言的生态及其运行时间,但总体而言,它有一些不错的好处。它使得这种事情变得非常简单:等待可能需要一些时间才能完成的操作。
它是如此简单,以至于创造了无数新的方法来坑人(blow ones foot off)。我想讨论的一种情况是,直到系统出现超载,你才意识到自己踩到了脚的那一种,这就是背压(back pressure)管理的主题。在协议设计中有一个相关术语是流量控制(flow control)。
什么是背压
(译注:back pressure,除了背压,还有人译为“回压”、“反压”)
在这里,我们要处理的东西在所有情况下或多或少都是相同的:我们有一个系统将不同组件组合成一个管道,而该管道需要接收一定数量的传入消息。
你可以想象这就像在机场模拟行李运送一样。行李到达,经过分类,装入飞机,最后卸下。在这过程中,一件行李要跟其它行李一起,被扔进集装箱进行运输。当一个集装箱装满后,需要将其运走。当没有剩余的集装箱时,这就是背压的自然示例。现在,放行李者不能放了,因为没有集装箱。
此时必须做出决定。一种选择是等待:这通常被称为排队(queueing )或缓冲(buffering)。另一种选择是扔掉一些行李,直到有一个集装箱到达为止——这被称为丢弃(dropping)。这听起来很糟糕,但是稍后我们将探讨为什么有时很重要。
但是,这里还有另一件事。想象一下,负责将行李放入集装箱的人在较长的时间内(例如一周)都没等到集装箱。最终,如果他们没有丢弃行李,那么他们周围将有数量庞大的行李。最终,他们被迫要整理的行李数量太多,用光了存储行李的物理空间。到那时,他们最好是告诉机场,在解决好集装箱问题之前,不能再接收新的行李了。这通常被称为
流量控制【4】,是一个至关重要的网络概念。
通常这些处理管道在每段时间内只能容纳一定数量的消息(如本例中的行李箱)。如果数量超过了它,或者更糟糕的是管道停滞,则可能发生可怕的事情。现实世界中的一个例子是伦敦希思罗机场 5 号航站楼开放,由于其 IT 基础架构无法正常运行,在 10 天内未能完成运送 42,000 件行李。他们不得不取消 500 多个航班,并且有一段时间,航空公司决定只允许随身携带行李。
背压很重要
我们从希思罗灾难中学到的是,能够交流背压至关重要。在现实生活中以及在计算中,时间总是有限的。最终人们会放弃等待某些事情。特别是即使某些事物在内部可以永远等待,但在外部却不能。
举一个现实的例子:如果你的行李需通过伦敦希思罗机场到达目的地巴黎,但是你只能在那呆 7 天,那么如果行李延迟成 10 天到达,这就毫无意义了。实际上,你希望将行李重新路由(re-routed)回你的家乡机场。
实际上,承认失败(你超负载了)比假装可运作并持续保持缓冲状态要好,因为到了某个时候,它只会令情况变得更糟。
那么,为什么在我们编写了多年的基于线程的软件时,背压都没有被提出,现在却突然成为讨论的话题呢?有诸多因素的结合,其中一些因素很容易使人陷入困境。
糟糕的默认方式
为了理解为什么背压在异步代码中很重要,我想为你提供一段看似简单的 Python asyncio 代码,它展示了一些我们不慎忘记了背压的情况: