@xuemingdeng
2016-12-30T03:53:57.000000Z
字数 2389
阅读 420
复杂的系统一般是由很多离散的部件组成,这些部件会出现故障,而且有时候会有多个部件同时出现故障,所以复杂的系统通常是运行在故障模式下。在一个采用了微服务架构的系统里,一个功能可能需要调用多个服务,因此每个部件的可用性决定了整个系统的可用性。这是弹性工程背后的核心逻辑。假设一个功能依赖三个服务,每个服务分别具有90%、95%和99%的可靠性,那么部分可用性就介于99.995%和84%之间(假设失效是单独发生的)。弹性工程意味着在设计时要把失效作为常规的考虑因素。
预测失效是弹性工程的第一步,而第二步是拥抱它们。告诉客户,可预知的失效好过未知或非期望的失效。回压是另一种弹性模式。从根本上说,回压就是要对资源强加限制。比如队列长度限制、带宽限制、流量控制、消息速度限制、消息大小限制等等。如果不显式地进行限制,它们就会变成隐式的(比如服务器的内存会被耗尽,不过因为这种限制是隐式的,所以无法准确地预测在什么时候会发生什么问题)。使用无边界的队列或其它一些隐式的限制就好比有人声称知道自己什么时候可以戒掉酒瘾,因为人总有一死,或许到了那个时候就不会再喝酒了。
速率限定不仅能够防止那些糟糕的actor破坏你的系统,它同时也是为了防止你自己对系统造成破坏。队列限制和消息大小限制是最有趣的,因为它们让很多开发者感到疑惑,同时也让他们感到沮丧,因为他们还没有完全搞清楚它们背后的动机。它们其实也是速率限定的另一个形式,或者说回压。下面我们拿消息大小限制作为例子。
假设我们有一个分布式系统,系统里的actor可以给其它actor发送消息,接收消息的actor对收到的消息进行处理,当然它们也可能往外发送消息。好的软件工程师都知道,分布式计算的第八个谬论是“均等网络”。所以,并不是所有的actor都使用相同的硬件、软件或者网络。我们有运行Ubuntu的拥有128G内存的服务器,有运行macOS的拥有16G内存的笔记本电脑,有运行Android的拥有2G内存的移动客户端,还有512M内存的物联网设备,在这些设备上面运行着各种各样的软件和网络接口。
如果我们不对消息的大小进行限制,那么我们就是在制造隐式的限制(上面我们对此做过讨论)。换句话说,你和你的交互方正在遵循一种无言的协议,双方都无法请求退出。因为任何一个actor都可以发送任意大小的消息,那么下游的消费者必须直接或者间接地支持任意大小的消息。我们怎么可能对任意大小的东西进行测试呢?我们做不到。我们只有两种选择:要么做出显式的限制,要么保持这种隐式的限制。如果选择了前者,我们可以定义我们的行为边界,并且对其进行测试。而后者要求我们基于未定义的生产规模进行测试,这是在拿系统可靠性作为赌注。第二种情况的限制依然存在,只不过被隐藏了起来。如果我们不让它们变成显式的,很容易在生产环境受到DoS攻击。在云基础设施环境中,因为它们的多租户特性,这些限制变得尤为重要。这些限制可以防止糟糕的actor(包括你自己)拖垮服务,或者垄断基础设施和系统资源。
在我们的异构actor系统里,我们针对移动设备和Web浏览器进行消息限制,它们一般都是单线程或内存受限的消费者。如果没有显式的消息大小限制,客户端可能会因为请求过多的数据或接收无法处理的数据而崩溃,这就是为什么有些协议虽然没有明确规定但必须存在。
让我们从企业的角度来看待这些问题。假设有另一个系统:美国国家高速公路系统。美国交通局通过Federal Bridge Gross Weight Formula来防止大量的汽车对道路和桥梁造成破坏。这里存在着相同的工程问题,只不过规则和基础设施不太一样。
2007年8月,明尼阿波里斯市的洲际35W密西西比河大桥坍塌,这个事故引起了人们对于卡车重量和大桥承受力之间关系的思考。2008年11月,美国国家交通安全局给出大桥坍塌的几个原因:有缺陷的加固板、不精确的勘察、过重的建材以及高峰时期的车流重量。
交通局依赖地磅来确保卡车重量与官方允许的重量合规,并对超重的车辆进行处罚。
官方规定的最大重量是80000磅。超过这个重量的卡车依然可以在高速上行驶,不过行程会受到限制。超重许可只会被发放给那些无法拆分至符合官方标准的货物,而且除了卡车以外没有其他方式可以运载这些货物。
重量限制需要被硬性规定,这样工程师们在建造道路、桥梁和其它基础设施时就有章可循。计算机系统也一样。这也就是为什么很多计算机系统硬性规定了很多限制。例如,Amazon就对他们的Simple Queue Service做了明确的限制——标准队列最多可承载12万个不落地的消息,而FIFO队列是2万个,而且消息大小被限制在256K以内。Amazon Kinesis、Apache Kafka、NATS和Google App Engine所使用的消息都限制在1M以内。系统设计者可以通过这些限制来优化他们的基础设施,并降低多租户环境存在的风险——虽然置之不理会让资源计划变得更简单。
不管是队列、消息大小、查询或者流量,不对它们进行限制是一种弹性工程反模式。不对它们进行显式地限制,故障会以不可预期和非期望的方式发生。要记住,限制其实是时刻存在的,只是有时候它们被隐藏起来了。通过显式的限制,可以让故障的发生更加地可预期,而且发生故障的平均时间变长,而从故障中恢复的时间变短,只要在事先多做一些稍微复杂一点的工作。
事先做出显式的限制,好过让系统在不可预期的情况下发生故障。后者虽然在前期会少作一些工作,但从长期来看会带来更多的问题。要求开发者们直接做出显式的限制,他们会因此认真思考他们的API和业务逻辑,并设计出稳定、可伸缩、高性能的交互系统。
查看英文原文:Take It to the Limit: Considerations for Building Reliable Systems