谈到分布式系统,协调是一个很慢的过程。无论我们在系统一致性,还是支持更高可用性,这都是事实。CRDT 是一种数据结构,为高度可用的系统提供强大的最终一致性,而无需协调成本。gossip 协议可以做到这一点。
如果读过关于state-based convergent CRDT 的任何内容,您可能会遇到“monotonic join semi-lattice”这个术语。尽管名字很难理解,但这个概念(来自秩序理论)是由熟悉的元素构成的。而不需要你有很高的数学知识才能理解 CRDT 是如何工作的,事实上,通过一些工作,有助于清楚的了解为什么基于 CRDT,可以依靠 gossip 协议使系统收敛到正确的值。
在这篇文章中,我将从构成 join semi-lattice 的元素开始来构建完整的概念。在下一篇文章中,我将解释基于状态的 CRDT 是如何根据我们从秩序理论中学到的经验教训来工作。
我们实际上只需要三个核心概念:
1.perfectly familiar: 小于或等于(写入≤)。这是在元素之间建立顺序的一种方式。如果我们可以说a≤b或b≤a,那么我们知道 a 等于 b。
2.incomparable: 在顺序理论中,如果 a 和 b无法比较,我们用 a || b 表示。这意味着我们不能使用≤来比较 a 和 b 。
3.join: 用 a∨b 表示。现在,如果你是 order theory 的初学者,可能不熟悉 join 的概念,但是也不用担心,之后会自然熟悉。
「Orders」
这篇文章承诺反 defang 秩序理论。所以我们开始试着了解秩序的含义。想象一下,我们同种类型元素的集合。比如,我们有一个整数集合。任何两个整数可以通过使用“小于或等于”相互比较。例如,3≤5。
你可以执行一个简单的练习:试着找到两个不能用这种方式进行比较的整数。对于整数的比较,总是会有顺序。
在秩序理论中,整数构成一个集合。而常见的整数的“小于或等于”比较关系称为二元关系。二元关系是涉及两个元素的运算符。现在我们准备就绪可以来定义 Orders:
Orders 是集合 S 上的二元关系≤,写为.
所以“小于或等于”是一种可能的二元关系。但还是有其他无数例子。例如,后代关系是二元关系的一种。比如一个女儿,母亲和祖父。我们可以将这些人中的任何两个人放入后代的二元关系中。例如:女儿≤母亲。
但请注意,对于后代来说,相对比较简单。下面通过你的朋友名单,我相信你会发现一些既不是你的祖先也不是你的后代的人。
在我们的第一个例子中,我们将≤定义为“小于或等于”。在第二个例子中,我们将它定义为“descendent-of”。在第一个例子中,我们的集合是所有整数。我们发现,对于任何两个整数,我们可以将它们与我们的二元关系进行比较。然而,在我们的第二个例子中,我们的集合是所有人的集合。而且有可能找到两个不能使用我们的关系进行比较的人。所以在秩序理论的语言中,称为无法比较体。当两个元素 a和 b 无法比较时,表示为a || b。
我们不仅介绍了前两个核心概念,还建立了我们需要讨论的两种 order :total order 和 partial order。如果对于集合中的任何 a 和 b,a≤b 或 b≤a,那么此集合为 total order 。小于或等于整数就是一个例子。
我们可以绘制一张代表这个命令的图表。由于我们没有无限的空间,因此我只关注从5到10的整数集合:
在上图中,我们可以在任何两个整数之间绘制箭头,箭头代表我们的二元关系。两个之间添加一个箭头。请记住,≤是传递性的,所以如果我们有
在 a 和 c 之间存在一个我们可以画的隐式箭头
partial order弱于total order。它不需要对集合中的每对 a 和 b进行比较。分序的一些例子是我们已经讨论过的后代关系,属/物种关系,以及我们现在可能会看到的“定位”关系。下图显示了一小部分位置的定位顺序:
你应该注意上图我们的元素被分成两个独立的块。左侧块中的元素和右侧块中的元素没有关系。
这里我们将≤定义为“located-in”。该图展示了一些地理位置比较。例如,西雅图≤美国和布鲁克林≤美国。注意,就像整数图一样,隐含了从布鲁克林到美国的箭头。同样,这是因为≤是传递性的。尽管我没在每一对可比较的元素之间绘制箭头,但是也能从图中看清楚。
与整数情况不同,我们还有“located-in”无法比较的对。例如,西雅图∥纽约、布朗克斯∥孟买。
现在我们来看一个与 CRDTs 更直观的分序的例子:矢量时钟。矢量时钟时间戳是整数时间戳的向量。我们可以像(4,3,1)那样写出它们。矢量的每个元素对应于一个节点或进程。所以,实质上,矢量时钟时间戳是我们感兴趣的所有节点或进程的逻辑时间戳集合。
在讨论矢量时钟时,我们将≤定义为“发生之前”。当且仅当v1中的每个元素小于或等于 v2中的对应元素时,矢量时钟时间戳v1发生在矢量时钟时间戳 v2之前。这是我们对“发生之前”的定义。例如,
(1,3,5)≤(1,4,6)
(2,7,2)≤(8,7,3)
我们可以如下图示发生之前的关系:
与定位一样,我们的图有两个独立的块。在这种情况下,每个块都有两个可以相互比较的元素。但是,如果我们有两个矢量时钟时间戳,而且都没有在另一个发生–之前,那么它们是无法比较的。因此,如果您尝试比较图表中两个块的元素,则会发现它们无法比较。拿(1,4,6)和(2,7,2)举例:
由于一个元素的一些(但不是全部)元素大于或等于另一个元素,所以我们不能将这两个时间标记置于“发生之前”关系中。我们得出结论(1,4,6)||(2,7,2)。它们无法比较。
现在我们已经定义了发生-之前的关系,我们可以创建一个更有趣的顺序图:
这里我们画了一组八个时间戳。发生-之前的关系定义了这个集合的部分顺序。重申一下,那是因为该集合中的某些元素可以根据发生的事情进行比较,但其他则不能。例如,(1,0,0)≤(1,0,1),而(1,0,0)||(0,0,1)。
「Joins」
我们三个核心概念中最重要的一个概念,就是 Join 。为了理解 Join,我们需要理解另一个概念就是元素的最大值。如果我们有一个集合,并且在该集合上有一个二元关系,那么最大值就是集合中的一个元素,该元素≥该关系中的任何一个其他元素。 通过查看另一个图表更容易看出:
在这个图表中,我们的集合由15个地点组成。地球是我们设定的最大值。选择集合中的任何位置,都位于地球上。没有其他的最大值。
如果您查看我们之前发生的向量时间戳,可以看到(1,1,1)是八个元素的向量时钟时间戳的上限。同样,这是因为我们可以选择集合中的任何其他元素,并且看到它在(1,1,1)之前发生。
所以现在我们准备定义一个 Join 。这里是技术定义(不用担心,我们会解开它):
对于集合 S,有序和两个元素a,b∈S,a 和 b(写成a∨b)的 join 是 S 的最小值,按照。
这意味着,当 a 和 b 的 Join 时,我们找到其他一些元素 x(可以是 a或 b),其中 a≤x 和 b≤x 。我们认为这是满足这个条件的最小元素x。所以,举个简单的例子,2∨5等于5.没有小于5的整数是2和5的最大值。注意7也是2和5的最大值,但它不是一个5≤7的最小值。
当我们处理一个 total order 时,就像整数小于或等于整数,两个元素的 join 总是等于这两个元素中的一个,那是因为它们可以直接比较。如果我们处理的是 partial order ,让我们再看看我们的定位图来说明:
孟买∨德里是什么?是孟买不在德里,德里也不在孟买。 因此,为了找到这两个元素的最小值,我们将不得不深入到我们的背景集中,在这种情况下,我们从图中设置了15个位置。由于地球是所有这些地点的最大值,所以地球是孟买和德里的最大值。但是,这不是最小的最大值。这是因为印度位于地球上,孟买和德里都位于印度。现在,孟买和德里都位于印度的位置。这意味着孟买∨德利=印度。
以下是我们到目前为止所考虑的一些 order 的图表:
希望这些例子能够很好地理解 join 是如何工作的。重要的是要注意加入者遵守三项原则(所有这些法律都与理解 CRDT 有关):
1)交换性:a∨b=b∨a
2)关联性:(a∨b)∨c=a∨(b∨c)
3)幂等性:a∨a= a
交换性很重要,因为它意味着元素的顺序并不重要。
关联性非常重要,因为它意味着在查找三个元素的连接之前,我们可以先开始将三个元素中的任意两个 join 起来,然后将该结果与剩余元素进行 join 。正如我们将在下一部分中看到的,这非常有用。
幂等性是重要的,因为它意味着无论我们将一个元素与自身 join 多少次,我们都会得到相同的结果。
谈到 CRDT 时,我们正在寻找的是能够以任何顺序和任意次数应用操作而不会破坏结果。通过 join而遵守的法律我们确定这一点。
现在我们了解了,join semi-lattic 的基础知识。我们首先看一下定义,然后分解它:
Join semi-lattic 是一个的次序,对于任何 x,y∈S 存在一个连接 x∨y 。
为了理解这个定义,我们需要理解三个概念:set,order 和 join,我们已经介绍了所有这些概念。我们只需要将一组集合看作一组元素。正如我们所看到的,命令是集合上的二元关系。所以如果我们的集合是整数,那么小于或等于是该集合上的一个命令。松散地说,它把它们按照这个顺序排列。如果我们有一个集合的顺序,那么我们可以使用箭头绘制图表来展示元素的相关性。最后,两个元素的 join 是这些元素按照某种顺序的最小值。
所以 join semi-lattic 是一种 order 。这意味着对于任何 semi-lattic,总会有一个对应的图。使它的顺序是我们可以从集合中获取任意两个元素并为它们找到一个联结。在整数小于或等于的情况下,直接看到我们有一个 join semi-lattice 。这是因为对于整数集合中的任何两个元素,两个整数中的一个或另一个将是最小值。对于任何总序,同样的想法都适用。当我们看分序时,事情会变得更有趣。
回到我们的之前顺序的图表,我们可以稍微想一想,对于任何两个元素都存在一个连接:
这里有些例子。在图表上找到它们并说服自己他们是正确的将会很有帮助。
(0,0,0)∨(0,0,1)=(0,0,1)
(1,0,0)∨(0,1,1)=(1,1,1)
(1,0,1)∨(1,0,1)=(1,0,1)
(0,1,0)∨(0,0,1)=(0,1,1)
但并不是所有的偏序都是 join semi-lattices 。对于这一点,请看下面的图表:
例如,(1,1,0)和(0,1,1)不存在连接。请记住,我们所说的背景集只包含图中显示的七个元素。这很重要,因为如果背景集合都是可能的矢量时间戳,那么我们总能找到任何两个元素的连接。
好。所以现在我们终于准备好定义 monotonic join semi-lattice 。
Monotonic join semi-lattice 是一种 join semi-lattice,听起来更加深奥。
在这种情况下,“monotonic”意味着每当我们加入 a∨b 时,我们就不会按照我们的顺序“向下”。 从我们的图表来看,我们来看一个例子,返回到 located-in order :
我们先从左下角的 Bronx 开始。如果你 join Bronx 和 Brooklyn,那么你会得到 NYC。所以你已经得到了 NYC 。如果你 join NYC,仍然会得到 NYC 。这是因为 join 是幂等的。
接着,如果你把 NYC 与 Seattle join 起来,那么你就会得到 US。如果你 join US 和 Mumbai,那么你必须一直向上得到 Earth,因为中间没有其他最大值。
在每个步骤中,您都会移动或停留在图表中的相同位置,无论您在每个步骤中选择了哪两个元素,情况都是如此。需要要注意的是,下面的说法是正确的:
Bronx≤NYC≤NYC≤US≤Earth。
可以做个实验,尝试以任意顺序进行join,始终将一个 join 的结果与任何其他您喜欢的元素 join 起来。按顺序写下结果。您应该能够在您的序列中的任意两个结果之间放置≤。这是理解什么是 join monotonic的一种方式。我们永远不会向下.
这对 CRDT 有什么意义?简而言之,就像 join 趋于“向上”一样,基于状态的 CRDT 合并也趋向于一个真实价值的融合,并且出于同样的原因。
「结论」
在这篇文章中,我们解释了 monotonic join semi-lattice 。有了这个概念,我们已经准备好 state-based convergent CRDTs 的文章了,这正是我们在第二部分要讲述的。