时间是零散的; 关于分布式系统的相似性和弱内存模型

大家好!

今天,我们想再次谈到在各种程序中同时执行和顺序执行的主题,尤其是在分布式系统中。 早在9月,我们就该主题发表了文章“ 同步性是一个神话 ”,现在我们正在发布更认真的研究的译文,我们希望它将帮助您更好地导航分布式系统。

在计算机科学中,只有一个真正的问题:承认缓存无效错误的名称不正确。 这些只是与时间使用有关的单位误差。
-未知作者

时间是一件奇怪的事。

这次真是太奇怪了,因为我们真的很想相信它已经完全精简了。 在我们看来,任何发生在15.00的事件(如我们所说)都在发生在16.00的任何事件之前-没有例外,论据或妥协。

但是,当有必要不那么严格地满足此要求时,计算机科学知道很多示例。 它体现在处理器,编译器和网络节点的级别。 在计算中,在堆栈的不同级别上,我们一次又一次地发现自己处于遇到两个事件的情况,而我们不知道它们以什么顺序发生。 时间显然不是全部。 她支离破碎。

怎么了 事实是我们不知道这一点,因为我们所存在的抽象级别不能为这个问题提供答案。 不管是否偶然,我们的计算抽象都不能保证程序的准确性。 重新安排事件的自由度通常使您可以创建效率更高且负担得起的系统。

处理器可能具有内存排序模型 ; 它反映了处理器在汇编阶段不希望给您任何保证的保证,例如,哪个指令在更早执行,哪个在以后执行。 处理器精确地决定了如何传达指令并无序执行它们-也就是说,它比我想象的更有效地使用其芯片。

语言可能具有内存匹配模型 (简称“内存模型”); 它反映了在生成程序集时(例如,在多个线程之间分配指令时)该语言不能提供给您的保证。 根据定义,这种重新排序是内存硬件模型所固有的,并且在很大程度上解释了为什么在编译器中提供了这种“弱”的时间概念。 它在以您编写非阻塞代码时所编程的语言实现的这种内存模型的框架之内。

在语言级别实现的内存模型的一个著名示例是C ++ 11标准中的强内存模型和弱内存模型 。 默认情况下,C ++为原子操作提供同步,但是它也会削弱内存访问模型以提高性能。 以这种方式提供的行为旨在作为对当今使用的主要处理器体系结构(x86,POWER和ARM)的抽象。

最后,分布式系统可能具有自己的一致性模型。 它反映了什么可以保证系统不会为您提供有关全局计算机网络中客户端和副本上事件的顺序的信息。 与通信延迟或缺乏同步直接相关的重新排序主要说明了为什么在分布式系统中如果没有上述弱时间模型就无法做到。 编写分布式应用程序时,您要编程的就是这种一致性模型。

实际上,在对分布式系统进行编程时,可以使用大量的一致性模型。 在所有这些情况下,这些模型都描述了从系统外部观察到的系统的(期望)行为。 如果我-一个特定的客户或特定的流-写入一个值,然后立即读取它,是否可以保证我一定会看到一个不比我的记录老的记录? 如果时间没有零碎,如果我们始终清楚系统中的操作按照什么顺序进行的话-自然,对这个问题的答案是肯定的。 根本问这样一个问题会很奇怪。

但是时间是零散的-因此,有必要提出这样一个问题。

一致性模型-我的意思是内存模型


谈论这样零散的秩序通常很困难,而且总是令人不愉快的。 我们想从一个事实开始,即在堆栈的各个级别上,时间始终是绝对绝对的-无论是ACID事务还是原子操作/锁。 担保越严格,自然就越容易用它们编程!

但是我们都为速度而努力。 无论是需要为了可用性而牺牲严格一致性的分布式系统,还是使用弱内存模型来避免同步成本的非阻塞编程,通常建议使用任何堆栈级别的程序员来处理这些复杂的参数。

共享内存模型的一致性和分布式内存模型的一致性都是抽象的 。 他们描述了使用该系统的程序员,该系统的接口。 鉴于现在我们认为理所当然的系统中事件顺序的一般属性不再起作用,它们使我们能够了解与弱内存模型相对应的行为类型。 看起来这两个记忆模型是相似的,但是,两个社区都开发了自己的讨论话题。 尽管它们重叠,但它们使用的值不同。

我们已经想象过这会是多么混乱。 怎么办

将时间描述为一个实体,意味着存在两种到八种偏序


塞巴斯蒂安·伯克哈特 Sebastian Burkhardt)在其2014年的书中试图详尽地描述一致性模型的众多选择。 具有这一特征以及其他数学结构,使用了事件逻辑顺序的两个变体:“可见性”和“仲裁”,先前在Burkhardt等人的其他著作中也提到过,例如,参见指向和检查的文章 。复制的数据类型(2014年)。

“可见性”是潜在条件固有的部分顺序。 它允许您跟踪哪些事件(可能在其他副本中)对哪些其他事件可见。 除了非周期性,对可见性没有其他要求; 一个对象中的事件对于另一个对象中的事件可能是可见的,并且读取或写入一个事件的操作不会影响其对其他事件的可见性。

“任意性”是一种通用顺序,使您可以跟踪发生某种选择情况的分布式系统如何判断哪个事件发生的更早和哪个发生的时间。

由于分布式一致性模型与内存模型相似,因此在讨论内存模型时,这种可见性和随机性现象也可以派上用场。 特别是,Burkhardt 在其2014年文章的附录中展示了C ++ 11的弱内存模型与因果一致性之间的接近程度,但存在一些有趣的偏差。 这将在文章的其余部分中讨论。

首先,让我们充实可见性和随机性,同时考虑“阅读”和“变更顺序”。 当“读取”时,仅在读取和写入都触摸同一对象的情况下,并且仅读取一个记录(或多个)时,才能看到任何两个对象之间的可见性。
这对应于这样一种情况,即在任何给定时间具有共享内存的处理器都可以在任何特定对象的一个​​存储单元中记录信息,即使不同的线程可以在不同的因果时刻访问该信息(另一方面,在分布式系统中,逻辑一个对象可以立即记录在许多单独的副本中)。

当具体化任意性时,“修改顺序”对应于同一阶段,这是客观的,并且仅允许记录。 同样,这种专门化基于这样一个事实,即在内存规范较弱的情况下,仅在一个对象的级别上给出了分类保证。

接下来,让我们讨论Burkhardt等人提出的一致性公理,并了解它们如何应用于弱记忆模型。 请注意:尽管有“轴”一词,但它们只是在各种内存模型中可能会或可能不会提供的属性。 Burkhardt的文章重点介绍确定跨对象因果关系的属性。

最终的连贯性


对于任何特定事件,不可能无限期地有很多事件没有看到它。 也就是说,任何事件最终对系统都是可见的。

在具有弱存储模型的系统中从逻辑上构建这样的条件应该更加困难:必须争论的是,对于任何特定记录 ,不可能有不计其数的不读取该记录或更早的记录(按修改顺序)的读取操作。

在C ++ 11规范中,尽管实际上很难找到反例,但不能保证符合该公理。

空灵的一致性


在流/客户端操作级别以及可见性/可读性方面跟踪“潜在条件”时,您需要了解没有返回时间。 这就是为什么在排序暗示阅读的流程时要求闭包是非循环的。 通常,毫无疑问将在分布式系统中观察到此属性,但是,如果系统的内存模型较弱,则此属性不允许某些推测版本的用户可见。

Burkhardt等人指出,该公理在C ++ 11规范中“未得到确认”,并且不清楚“未验证” 在实践中是否可以观察到 “令人满意的循环”。

条件性公理


要指定在弱记忆模型下条件现象到底与什么相关,我们必须精确地确定哪些事件可以影响其他事件的结果。 首先,请考虑我们的标准因果公理: 会话保证 。 这是四个相互关联的品质,反映了发生在不同流中的读写操作的一致性,此外,还应在每个对象的级别上指定它们(请参阅Burkhardt等图23 )。

  • RYW(读取记录):写入操作之后的读取操作是在同一单元中,同一流/副本/会话内完成的,它必须读取的数据与记录相关。 分布式系统的此属性的变体专门根据可见性指定,而弱存储模型的变体应基于读取顺序和更改顺序。
  • MR(整体读数):以后的读数(在同一数据流中,在同一单元格中)将来也应看到同样重要的数据。
  • WFR(先读取,然后写入):如果写入在流中的读取之后,在同一单元中,则按照更改的顺序,它应晚于读取操作。
  • MW(单记录):以后的记录(在流中,在同一单元格中)应按修改顺序在以后记录。

WFR和MW的原始版本有两个版本,以保证随机性和可见性。 但这仅在处理比整数寄存器更复杂的数据单元时才重要。

这些属性反映了条件性的概念,符合我们的常识。 但是,他们错过了最有趣的事情。 特别是在弱存储模型中进行分析时,这种条件性现象会受到流/副本/会话以及进行输入的特定单元/对象的限制: 在Burkhardt等人的文章中 。 在这种情况下,关于“逐个条件的条件可见性”和“逐个条件的任意任意性”的说法,也见图。 23.当不同的流将信息写入不同的单元时,这些现象并没有完全限制系统的行为。

然后,跨对象条件的公理描述了因果关系在各种对象/存储单元级别的影响。

  • COCV(跨​​对象条件可见性):与RYW相同,但不存在必须在同一线程/副本/会话中全部完成最终读取的条件。 客观上晚于该对象中记录的对象读取的数据应与在记录期间输入的数据相关。

C ++ 11规范反映了这些属性。 请注意:它们的定义方式使得对记录可见性的限制和修改顺序的任意性不会对这些定义产生太大影响。

但这不适用于后者。

  • COCA(跨对象条件任意):类似于单片记录,但适用于不同的流,类似于COCV-对于不同的流,它是RYW。 但是,由于修改顺序仅影响一个对象中的记录,因此弱存储模型的表述允许系统在不同对象中记录事件的分配不一致,并且记录可能既不对应于流中的读数也不对应于顺序。

具体地说,弱内存模型中的COCA是一个弱得多的属性。 这就是为什么对于弱内存模型,以下代码可能返回{x ≡ 0, y ≡ 0}

Thread A: y := 0; x := 1; return x
Thread B: x := 0; y := 1; return y


每个流中的顺序可能与按对象顺序和修改顺序不一致。 请注意:对于RYW,修改顺序中不存在x := 0 → x := 1 ,对于y是相同的。 因此,修改顺序应包含x := 1 → x := 0y := 1 → y := 0 。 因此,修改顺序显然按流程顺序形成一个循环。
在带有弱存储模型的COCA中允许这样的循环。 并不是说流/读取的顺序与修改的顺序相反,而是每个流看到一致的记录历史。 仅当我们客观地限制其应用范围时,这些故事才与其他流程的故事保持一致。

这一切是什么意思?


时间是零散的。

即使在我们看来,时间以一种非常有序的方式流动,但是研究分布式系统和薄弱的内存模型显然可以告诉您事实并非如此。 这就是为什么在这两种情况下,根据总的时间,我们的标准过度逼近会限制性能-这是我们无法承受的。
然后,认识到时间确实是零散的,我们发现这种偏爱的品种之间有许多微小但重要的差异。 即使乍一看似乎很相似的上述两个领域,在许多细微差别上,也有可能区分哪些特定类型的事件被认为是相互影响的。

在有人可以用另一种语言表达一个领域的属性之后,有必要更详细地了解各种属性的技术细节。

时间是零散的。 也许我们只需要习惯它。

Source: https://habr.com/ru/post/zh-CN430926/


All Articles