论文地址:Blazes:分布式程序的协调分析

    对于许多实践者来说,分布式一致性是系统性能和规模可管理性的最关键问题。

    在Blazes中,Alvaro 等人。重新审视一下“分布式系统开发人员的一个紧迫问题”,即容错服务的分布式一致性机制的正确性和效率。我们需要足够的协调来避免异常情况,但理想情况下不需要超过这一点,因为否则协调开销将降低性能。那么,我们如何知道多少才足够,如何避免过度协调呢?

    Blazes分析分布式系统的基于数据流的模型(基于组件及其交互)。如果程序是用Bloom编写的,则可以从程序本身推断出模型。对于Java,使用一小组注释来指定模型特性。从模型中,Blazes找出需要协调的地方,并生成执行该协调所需的代码(尽管本文未提供生成代码的详细信息)。

    可以推断,“Blazes引擎”将模型描述作为输入,并生成一个协调规范作为输出,该规范反过来可以输入到代码生成器中。这听起来像是对分布式系统工程师工具包的一个很好的补充,也是对TLA+(Amazon使用的)等工具的一个很好的补充,无论源模型格式或代码生成的具体实施方式如何。事实上,即使没有代码生成,分析结果也肯定会很有趣。

    BLAZES利用的关键直觉是,即使组件是顺序敏感的,也常常可以在不牺牲一致性的情况下避免全局排序的成本。在许多情况下,BLAZES可以通过一个更高效、更易于管理的协议(生产者和消费者之间的异步点对点通信,称为sealing)来确保一致的结果,该协议指示流的分区何时停止更改。

    组件是一个可能有状态的计算单元,处理输入和输出流。流是无限的、无序的消息集合。组件和流是逻辑结构,可以物理地体现在多个实例中。模型的另一个重要部分是在流中嵌入标点符号的能力(支持windows)。

    标点符号保证生产者不会在流的特定逻辑分区内生成更多的消息。

    如果希望能够容忍某个给定状态实例的丢失,则必须能够从仍然具有的其他内容(重播策略)中重新创建该状态,或者必须在其他位置具有该状态的另一个副本(复制)。容错要求分布式系统支持至少一种重播和复制策略。非确定性消息传递以微妙的方式与之交互,在缺乏充分协调的情况下引入各种系统异常。

    • 运行异常是指一个组件在相同的输入上以不同的运行方式产生不同的输出
    • 当复制的组件实例在同一执行过程中对同一输入产生不同的输出时,就会发生实例异常。
    • 发散异常是指多个副本的状态变得永久不一致的异常。

    如果一个组件使用单调逻辑(如CALM定理中提出的),那么它将产生确定性的结果,尽管输入顺序是非确定性的。这听起来像是一件很有用的事情,知道什么时候需要多少协调。

    对于不汇合的组件,我们可以使用排序或密封(禁止组件生成输出,直到它们的所有输入都到达为止,与标点流一起使用)。

    在Blazes模型中,组件被注释为合流(C)或顺序敏感(O),以及只读(R,无状态)或写路径(W,有状态)。

    CR注释表示通过组件的路径是汇合的和无状态的;也就是说,不管其输入顺序如何,它都会生成确定的输出,并且其输入不会修改组件的状态。CW表示一条合流且有状态的路径。注释ORgate和OWgate分别表示无状态或有状态的非汇合路径。

    可选的门下标标识用作分区键的属性,以标识组件在其上操作的分区。例如,CRDTs将被建模为CW。

    流可以用Seal(key)注释进行注释,该注释指示流被标点在流属性的子集键上。Rep注释指示复制流。

    Blazes使用组件和流注释来确定一个给定的数据流是否保证产生确定的结果;如果它不能保证这一点,它就用协调代码来扩充程序。

    当Blazes发现异常的可能性时,它选择了避免异常所需的最小协调策略。

    Blazes将通过限制消息如何传递到某些组件来自动修复不合流或不合流的数据流。在可能的情况下,BLAZES将识别密封流和组件语义之间的兼容性,综合一种避免全局协调的基于密封的策略。否则,它将强制向这些组件传递消息时的总顺序。

    通过消除“过于谨慎”的协调,Blazes提高了程序性能(吞吐量):

    保守地部署事务拓扑的开销是相当大的。在5节点部署中,非协调数据流的峰值吞吐量约为其协调对等数据流的1.8倍。当我们将集群扩展到20个节点时,吞吐量的差异将增加到3倍。

    阿尔瓦罗等人,用火焰来协调。认为布局成为分布式系统工程师需要关注的最大遗留问题之一。

    有关数据放置策略的经验法则通常涉及预测显示空间和时间局部性的访问模式;一起访问的数据项应彼此靠近,并且应缓存频繁访问的数据项。

    最后,对于给定的模型,Blazes可以确定所需的协调。但我们怎么知道一开始我们有没有好的模特呢?另一种配方会更好吗?

    一些设计模式是从我们的讨论中产生的。首先,在可能的情况下,复制应该放在合流组件的上游。因为它们可以容忍所有的输入顺序,所以廉价的复制策略(如gossip)足以确保合流输出。类似地,缓存应该放在汇合组件的下游。由于此类组件从不收回输出,因此可以使用简单的、仅附加的缓存逻辑。更具挑战性和吸引力的是将这些设计原则捕获到编译器中并自动重写数据流的可能性。