队列无法修复过载(2014)

2020-05-13 17:19:22

人们总是滥用排队。最令人震惊的案例是修复速度较慢的应用程序的问题,从而导致过载。但要说明原因,我需要带上一些我在这个地方的演讲和文本,以及我在愤怒中用Erlang写的更详细的内容。

为了将事情过于简单化,我最终从事的大多数项目都可以想象成一个非常大的浴室洗手池。用户和数据输入从水龙头向下流动,直到系统输出:

因此,在正常操作下,您的系统可以处理传入的所有数据,并很好地执行这些数据:

水进了,水出了,每个人都很开心。但是,有时您会看到系统上出现临时过载。如果你发信息,这将围绕着体育赛事或新年前夕这样的事件展开。如果你是一个新闻网站,那就是当一件大事发生的时候(美国的选举,英国的王室宝宝,有人说他们不喜欢把法语作为魁北克的一种语言)。

系统输出的数据仍然有限,输入的速度越来越快。在这一点上,Web用户将使用诸如缓存之类的东西来实现它,这样就可以减少所需的输入和输出。其他系统将使用巨大的缓冲区(队列,在本例中为接收器)来保存临时数据。

当你不可避免地遇到长期超载时,问题就来了。这是当你看着你的系统负载,哦,天哪,它永远不会掉下来。结果发现奥巴马不想交出他的出生证明,王室宝宝看起来不像他的父亲,有人说魁北克应该更好地使用巴黎法语,而且谣言一次传了好几天甚至几周:

突然之间,缓冲区、队列之类的东西再也处理不下去了。您正处于危急状态,您可以看到服务器冒烟,或者如果在云中,情况和往常一样糟糕,但情况会更糟!

哎呀,大家都死了,你凌晨3点就在办公室里(他在美国认识这么多人,厌恶他们的肯尼亚总统,现在想知道关于王室宝宝的消息,而魁北克人却因为某种原因抬头看着带奶酪宝宝的皇家鸡尾酒)。

您可以查看堆栈跟踪、队列、DB慢查询和您调用的API。你一次要花几周的时间来优化每个组件,确保它始终是好的和坚实的。事情一直在崩溃,但你每次都要多花2-3天的时间。

最后,你会发现问题堆积如山,但每一次失败之间相隔一周,这会极大地减慢你的优化速度,因为当事情需要几周的时间才会变坏的时候,衡量它们是非常困难的。

你去吧,好吧,我已经没有主意了,让我们买一台更大的服务器吧。系统最终看起来是这样的,但它还是出了故障:。

除了现在,它是一块无法维护的垃圾,里面装满了肮脏的黑客,使它工作起来的成本是以前的5倍,而且你已经拿了几个月的钱来优化它,没有任何该死的原因,因为它在超载时仍然会死亡。

问题出在哪里?那边的那个红色箭头。你达到了一些硬限制,即使在你所有的分析过程中,你也没有正确地考虑到这一点。这可以是数据库、外部服务的API、磁盘速度、带宽或常规I/O限制、分页速度、CPU限制等等。

你花了几个月的时间来优化你的超级服务,结果却在某个时候发现,你没有更大的变化就超过了它的最佳速度,当你的系统运行速度超过这个硬限制的那一天,你注定要让自己陷入一系列永恒的系统故障。

令人沮丧的是,您发现一旦您的系统流行起来,就会有人使用它和它的API,而要将其更改为更好的系统是非常昂贵和困难的。特别是因为你可能不得不重新审视你在其核心设计中所做的假设。哎呀。

那你需要什么?当事情变坏时,你需要挑选必须给予的东西。你将不得不在阻塞输入(背压)或将数据丢到地板上(减负)之间做出选择。在现实世界中,这种情况时有发生,我们只是不想以开发人员的身份这么做,好像这是在承认失败。

俱乐部前的保镖,绕过水坝的溢水道,防止你在满油箱里加更多汽油的压力机制,等等。他们都在那里实施系统范围的流量控制,以确保操作安全。

在[非关键]软件中?管他呢!我们从不卸货,因为这会激怒利益相关者,我们也从不考虑背压。通常,系统中的背压是隐含的:很慢。

对某事物的函数/方法调用最终会花费更长的时间吗?它很慢。没有足够多的人认为这是通过你的身体系统的背压。事实上,低速分布式系统往往是超载煤矿的警戒线。问题是,每个人都只是站在那里走着,为什么一切都这么慢??&34;而开发人员走了,我不知道!。就是这样!这很难,好吗!&34;

这通常是因为系统中的某个地方(可能是网络,或者在没有适当工具的情况下几乎不可能观察到的东西,如TCP incast),有些东西被阻塞了,其他所有东西都会将其推回到系统的边缘,给用户。

背压会让系统变慢吗?它降低了用户输入数据的速度。这可能会让你的整个堆栈保持活力。你知道人们什么时候开始使用排队吗?就在那儿。当操作时间太长并阻塞了东西时,人们就会在系统中引入一个怪异的队列。

而且效果立竿见影。曾经运行缓慢的应用程序现在又恢复了速度。当然,您需要重新设计整个界面以及交互和报告机制,以使其成为异步的,但这真是太快了!

除非在某个时候队列溢出,您会丢失所有数据。这是一个严肃的会议,每个人都会在会上讨论这是怎么可能发生的。Dev#3建议添加更多的Worker,Dev#6建议队列获得持久性,这样当它崩溃时,不会丢失请求。

大家都说很酷。去上班了。除非到了某个时候,系统又死了。队伍又回到了原来的位置,但是已经挤满了人。Dev#5走进去想,哦,对了,我们可以增加更多的队列(我发誓,我在不太了解的时候就看到过这一幕的出现)。人们说,哦,是的,这会增加运力,然后他们就出发了。

然后它又死了。没有人想到那里那个狡猾的红色箭头:

也许他们在不知情的情况下决定使用MongoDB,因为MongoDB比Postgres(呵呵)快。谁知道呢。

真正的问题是涉及到的每个人都使用队列作为优化机制。有了他们,新的问题现在成了系统的一部分,这是一个难以维持的噩梦。通常,这些问题会以破坏端到端原则的形式出现,因为使用持久队列作为一种即发即忘机制,或者假设任务不能重放或丢失。您有更多的地方可以超时,需要新的方法来检测故障并将其传回给用户,等等。

这些是可以解决的,别误会我的意思。问题是,它们是作为一个解决方案的一部分引入的,而这个解决方案并不适合它所要解决的问题。所有这些都只是过早的优化。即使每个参与的人都采取了措施,对真正痛点的真正失败做出了反应,等等。问题是,没有人考虑到事情的真正、核心业务结束是什么,以及它的限制是什么。人们或多或少地在每个子组件中局部地考虑了这些限制,但并不总是这样。

但应该有人做出必须做出的选择:你是阻止人们在系统中输入东西,还是减轻负担。这些都是不可避免的选择,不采取行动会导致系统故障。

你知道什么很酷吗?如果你发现你的系统中确实存在这些瓶颈,并把它们放在适当的背压机制后面,你的系统甚至没有权利变慢。

步骤1.确定瓶颈。步骤2:向瓶颈请求堆积更多数据的权限:

根据您放置探头的位置,您可以针对不同级别的延迟和吞吐量进行优化,但您要做的是定义系统的适当操作限制。

当人们盲目地应用队列作为缓冲区时,他们所做的就是创建一个更大的缓冲区来积累传输中的数据,结果迟早会丢失。你让失败变得更加罕见,但你却让失败变得更严重。

当您卸载并为您的系统定义适当的操作限制时,您就没有这些了。您可能会遇到同样不满的客户(因为在任何一种情况下,他们都不能正确履行您的系统承诺),但通过适当的背压或减负,您可以获得:

一个API在设计时会考虑到这两种情况之一(背压会让您知道何时处于超载状态,以及何时重试等等,而卸载会让用户知道有些数据丢失了,这样他们就可以解决这个问题)。

为了使内容可用,考虑到端到端原则的适当的幂等API将使其成为问题,因此这些背压和减负的实例对您的调用者来说不应该是问题,因为他们可以安全地重试请求并知道它们是否起作用。

因此,当我抱怨/反对队列时,那是因为队列的应用方式通常(但不总是)会毫无理由地完全扰乱端到端原则。这是因为糟糕的系统工程,人们试图让一辆18轮的卡车通过吸管,并想知道为什么地狱的事情会变得糟糕。最后,排队只会让事情变得更糟。当情况恶化时,情况会变得非常糟糕,因为每个人都试图闭上眼睛,忽视他们为了解决大坝上游的洪水问题而修建了一座大坝的事实。

当然,还有一种用例使用队列作为前端线程/进程之间的消息传递机制(想一想PHP、Ruby、CGI应用程序,等等),因为您的语言不支持进程间通信。它略好于使用MySQL表(我见过几次,甚至参与过),但比选择一个支持正确实现解决方案所需的消息传递机制的工具要差得多。