Distributed systems for fun and profit翻译-3.Time and Order(待校)

原文地址:http://book.mixu.net/distsys/time.html

什么是顺序,为何重要?

您是什么意思“顺序是什么”?

我的意思是,为什么我们一开始就如此着迷于秩序?我们为什么要关心A是否在B之前发生?我们为什么不关心其他属性,例如“颜色”?

好吧,我疯狂的朋友,让我们回到分布式系统的定义来回答这个问题。

您可能还记得,我将分布式编程描述为解决可以在使用多台计算机的单台计算机上解决的同一问题的艺术。

实际上,这是对秩序的痴迷的核心。任何一次只能做一件事的系统都会创建总的操作顺序。就像人们穿过一扇门一样,每个操作都将具有明确定义的前任和后继。这基本上是我们一直努力保持的编程模型。

传统模型是:一个程序,一个进程,一个CPU上运行一个内存空间。操作系统抽象出以下事实:可能存在多个CPU和多个程序,并且计算机上的内存实际上在许多程序之间共享。我并不是说线程编程和面向事件的编程都不存在;只是它们是“一个/一个/一个”模型之上的特殊抽象。程序被编写为以有序的方式执行:您从顶部开始,然后向下到底部。

订单作为一种属性已受到了广泛的关注,因为定义“正确性”的最简单方法是说“它的工作方式与在单台机器上的工作方式相同”。这通常意味着a)我们运行相同的操作,b)我们以相同的顺序运行它们-即使有多台计算机。

保留顺序(为单个系统定义)的分布式系统的好处是它们是通用的。您无需在意什么是操作,因为它们将完全像在单台计算机上一样执行。这很棒,因为您知道无论执行什么操作都可以使用同一系统。

实际上,一个分布式程序运行在多个节点上。带有多个CPU和多个操作流。您仍然可以分配总订单,但是它需要准确的时钟或某种形式的通信。您可以使用完全准确的时钟为每个操作加上时间戳,然后使用该时钟来计算总顺序。或者,您可能具有某种通讯系统,可以按总顺序分配顺序号。

全部和部分订单
分布式系统中的自然状态是偏序。网络和独立节点都不保证相对顺序。但是在每个节点上,您都可以观察到本地订单。

甲总订单是一个二元关系,它定义为在某个集合的每个元素的顺序。

两个不同的元件是可比较的,当它们中的一个比另一个大。在部分排序的集合中,某些元素对不具有可比性,因此部分排序不会指定每个项目的确切顺序。

总阶和偏阶都是传递和反对称的。以下语句对于X中的所有a,b和c都具有总顺序和部分顺序:

如果a≤b且b≤a,则a = b(反对称);
如果a≤b且b≤c,则a≤c(传递性);
然而,订单总额共计:

X中所有a,b的a≤b或b≤a(总计)
而部分顺序只是反身的:

X中所有a的a≤a(反射率)
请注意,总体意味着反思。因此,部分订单是总订单的较弱变体。对于部分顺序的某些元素,totality属性不成立-换句话说,某些元素不可比较。

Git分支是部分顺序的示例。您可能知道,git版本控制系统允许您从单个基础分支(例如,从主分支)创建多个分支。每个分支代表基于共同祖先派生的源代码更改的历史记录:

[分支A(1,2,0)] [主(3,0,0)] [分支B(1,0,2)]
[分支A(1,1,0)] [主(2,0,0)] [分支B(1,0,1)]
\ [主(1,0,0)] /
分支A和B源自一个共同的祖先,但它们之间没有明确的顺序:它们代表不同的历史,并且如果不进行其他工作(合并)就无法简化为单个线性历史。当然,您可以将所有提交按任意顺序放置(例如,先按祖先对它们进行排序,然后通过在B之前将A排序或在A之前将B排序来打破联系),但是如果强制执行不存在的总排序,则会丢失信息。

在一个由一个节点组成的系统中,总的顺序必然出现:在单个程序中以特定的,可观察的顺序执行指令并处理消息。我们开始依赖这个总顺序-它使程序的执行是可预测的。可以在分布式系统上维护此命令,但要付出代价:通信昂贵,时间同步又困难又脆弱。

时间是什么?
时间是秩序的源泉-它使我们能够定义行动的秩序-巧合的是,人们也可以理解一种解释(一秒钟,一分钟,一天等等)。

从某种意义上讲,时间就像任何其他整数计数器一样。恰好足够重要,因为大多数计算机都具有专用的时间传感器,也称为时钟。至关重要的是,我们已经弄清楚了如何使用一些不完善的物理系统(从蜡蜡烛到铯原子)来合成相同计数器的近似值。所谓“合成”,是指我们可以通过某种物理属性在物理上相距较远的地方近似整数计数器的值,而无需直接进行通信。

时间戳确实是代表从宇宙开始到当前时刻的世界状态的简写值-如果某件事发生在特定的时间戳上,那么它可能会受到之前发生的所有事情的影响。可以将此想法推广为一个因果时钟,该时钟明确地跟踪原因(依赖性),而不是简单地假设时间戳之前的所有内容都是相关的。当然,通常的假设是我们只应该担心特定系统的状态,而不是整个世界。

假设时间在任何地方都以相同的速度进行-这是一个大的假设,我将在稍后再次谈到-在程序中使用时间和时间戳有几种有用的解释。三种解释是:

订购
持续时间
解释
订购。当我说时间是秩序的来源时,我的意思是:

我们可以将时间戳附加到无序事件以对其进行排序
我们可以使用时间戳来强制操作的特定顺序或消息的传递(例如,如果操作顺序混乱,则延迟操作)
我们可以使用时间戳记的值来确定某件事是否按时间先后发生
解释 -时间是一种普遍可比的价值。时间戳记的绝对值可以解释为日期,这对人们很有用。给定一个从日志文件开始停机的时间戳,您可以说是上个星期六,当时有雷暴。

持续时间 -时间测量的持续时间与现实世界有些关系。算法通常不关心时钟的绝对值或将时钟解释为日期,但它们可能会使用持续时间来进行某些判断。特别地,等待所花费的时间量可以提供有关系统是已分区还是仅经历高延迟的线索。

就其性质而言,分布式系统的组件的行为并非可预测。他们不保证任何特定的顺序,提前率或没有延误。每个节点的确有一些本地顺序-因为执行(大致)是顺序的-但是这些本地顺序彼此独立。

施加(或假设)顺序是减少可能执行和可能发生的空间的一种方法。当事物可以以任何顺序发生时,人类很难思考事物-只是考虑了太多的排列。

时间到处都以相同的速度进行吗?
根据我们个人的经验,我们都有一个直观的时间概念。不幸的是,这种直观的时间概念使描绘整体顺序而不是局部顺序更加容易。想像一个序列接连发生而不是同时发生的序列比较容易。对于消息的单个顺序进行推理,要比对消息以不同顺序和不同延迟到达进行推理要容易得多。

但是,在实施分布式系统时,我们希望避免对时间和顺序进行严格的假设,因为这些假设越强,“时间传感器”或车载时钟发出的系统就越脆弱。此外,下订单会带来成本。我们可以忍受的时间性越不确定,就越可以利用分布式计算。

对于“时间是否到处都以相同的速度增长?”这个问题有三个常见的答案。这些是:

“全球时钟”:是
“本地时钟”:否,但是
“没有时钟”:不!
这些大致与我在第二章中提到的三个时序假设相对应:同步系统模型具有全局时钟,部分同步模型具有本地时钟,在异步系统模型中根本不能使用时钟。让我们更详细地了解这些。

假设时间为“全球时钟”
全局时钟假设是存在一个具有完美准确性的全局时钟,并且每个人都可以使用该时钟。这就是我们倾向于思考时间的方式,因为在人与人之间的互动中,时间的微小差异并不重要。

全球时钟

全局时钟基本上是总顺序的源(即使所有节点从未通信,所有节点上每个操作的确切顺序)。

但是,这是一个理想的世界观:实际上,时钟同步只能在有限的精度范围内进行。这受到商用计算机中时钟准确性缺乏,使用时钟同步协议(例如NTP)时的等待时间以及时空性质的限制。

假设分布式节点上的时钟是完全同步的,则意味着假定时钟以相同的值开始并且永不漂移。这是一个很好的假设,因为您可以自由地使用时间戳来确定全局总阶数-受时钟漂移而不是延迟的约束-但这是一项不平凡的操作挑战,并且是潜在的异常来源。在许多不同的情况下,简单的故障-例如用户不小心更改了计算机上的本地时间,或者过时的计算机加入了群集,或者同步时钟的漂移速率略有不同,等等,这可能会导致故障。 -跟踪异常。

尽管如此,还是有一些现实世界的系统做出了这种假设。Facebook的Cassandra是假设时钟同步的系统的示例。它使用时间戳来解决写入之间的冲突-带有较新时间戳的写入将获胜。这意味着,如果时钟漂移,则新数据可能会被旧数据忽略或覆盖。同样,这是一个操作上的挑战(据我所知,这是人们敏锐意识到的挑战)。另一个有趣的例子是Google的Spanner:该论文描述了其TrueTime API,该API可以同步时间,但也可以估计最坏情况下的时钟漂移。

带有“本地时钟”假设的时间
第二个,也许更合理的假设是,每台机器都有自己的时钟,但是没有全局时钟。这意味着您不能使用本地时钟来确定远程时间戳是在本地时间戳之前还是之后发生的;换句话说,您无法有意义地比较两台不同计算机上的时间戳。

当地时钟

本地时钟假设与现实世界更加接近。它分配了部分顺序:每个系统上的事件都是有序的,但不能仅通过时钟在整个系统上对事件进行排序。

但是,您可以使用时间戳在单台计算机上订购事件。只要注意不要让时钟跳动,您就可以在一台机器上使用超时。当然,在最终用户控制的机器上,这可能是假设太多了:例如,用户在使用操作系统的日期控件查找日期时,可能会不小心将其日期更改为其他值。

假设时间为“无时钟”
最后,还有逻辑时间的概念。在这里,我们根本不使用时钟,而是以其他方式跟踪因果关系。请记住,时间戳只是当时世界状况的简写-因此,我们可以使用计数器和通讯来确定是在其他事物发生之前,之后还是同时发生。

这样,我们可以确定不同机器之间的事件顺序,但是不能说任何有关间隔的信息,也不能使用超时(因为我们假设没有“时间传感器”)。这是部分顺序:可以使用计数器在一个系统上对事件进行排序,而无需进行通信,但是跨系统对事件进行排序需要消息交换。

Lamport在时间,时钟和事件排序方面的论文是分布式系统中引用最多的论文之一。矢量时钟是该概念的概括(我将在后面详细介绍),它是一种无需使用时钟即可跟踪因果关系的方法。卡桑德拉(Cassandra)的表兄弟Riak(Basho)和Voldemort(Linkedin)使用矢量时钟,而不是假设节点可以访问具有完美准确性的全局时钟。这使那些系统可以避免前面提到的时钟精度问题。

当不使用时钟时,可以在远处的机器上订购事件的最大精度受通信延迟的限制。

在分布式系统中如何使用时间?
时间的好处是什么?

时间可以定义整个系统的顺序(无需通信)
时间可以为算法定义边界条件
事件的顺序在分布式系统中很重要,因为分布式系统的许多属性都是根据操作/事件的顺序定义的:

正确性取决于(约定)正确的事件顺序,例如分布式数据库中的可序列化性
当发生资源争用时,order可以用作平局决胜局,例如,如果窗口小部件有两个订单,则履行第一个订单并取消第二个订单
全局时钟将允许对两台不同机器上的操作进行排序,而无需两台机器直接通信。没有全局时钟,我们需要进行通信才能确定顺序。

时间也可以用于定义算法的边界条件-具体来说,是要区分“高延迟”和“服务器或网络链路已关闭”。这是一个非常重要的用例。在大多数实际系统中,超时用于确定远程计算机是否发生故障,或者仅是经历了较高的网络延迟。做出此决定的算法称为故障检测器;我将很快讨论它们。

矢量时钟(因果顺序的时间)
之前,我们讨论了有关分布式系统中时间进度的不同假设。假设我们无法实现准确的时钟同步-或从我们的系统不应该对时间同步问题敏感的目标开始,我们如何订购商品?

Lamport时钟和矢量时钟是物理时钟的替代品,物理时钟依赖于计数器和通信来确定整个分布式系统中事件的顺序。这些时钟提供了一个在不同节点之间可比的计数器。

兰伯特时钟很简单。每个进程都使用以下规则维护一个计数器:

只要某个进程有效,就增加计数器
每当进程发送消息时,包括计数器
收到消息后,将计数器设置为 max(local_counter, received_counter) + 1
表示为代码:

函数LamportClock(){
this.value = 1;
}

LamportClock.prototype.get = function(){
返回this.value;
}

LamportClock.prototype.increment = function(){
this.value ++;
}

LamportClock.prototype.merge = function(other){
this.value = Math.max(this.value,other.value)+1;
}
甲兰波特时钟允许计数器跨系统相比,具有一个警告:兰波特时钟定义的部分顺序。如果timestamp(a) < timestamp(b):

a可能在之前b或之前发生过
a 可能与 b
这称为时钟一致性条件:如果一个事件先于另一个事件,则该事件的逻辑时钟先于其他事件。如果a和b来自相同的因果历史,例如,两个时间戳值都是在相同的过程中产生的;或者b是对发送的消息的回应,a那么我们知道a之前发生过b。

直观地讲,这是因为Lamport时钟只能传送有关一个时间轴/历史记录的信息。因此,比较从未相互通信的系统的Lamport时间戳可能会导致并发事件在没有时并发出现。

想象一下一个系统,在最初的一段时间后,该系统分为两个彼此不通信的独立子系统。

对于每个独立系统中的所有事件,如果a发生在b之前,则ts(a) < ts(b); 但是,如果您从不同的独立系统中获取两个事件(例如,没有因果关系的事件),那么您将无法说出有关其相对顺序的任何有意义的信息。虽然系统的每个部分都为事件分配了时间戳,但这些时间戳之间没有任何关系。即使两个事件不相关,也可能看起来是有序的。

但是-这仍然是一个有用的属性-从单台机器的角度来看,与发送的任何消息ts(a)都将收到的响应,ts(b)其响应为> ts(a)。

向量时钟是Lamport时钟的扩展,它维护[ t1, t2, … ]N个逻辑时钟的数组-每个节点一个。每个节点都不会在每个内部事件上将其自己的逻辑时钟在矢量中增加一个,而不是增加一个公共计数器。因此,更新规则为:

只要进程确实起作用,就在向量中增加节点的逻辑时钟值
每当进程发送消息时,请包括逻辑时钟的完整向量
收到消息后:
将向量中的每个元素更新为 max(local, received)
增加代表向量中当前节点的逻辑时钟值
再次表示为代码:

函数VectorClock(value){
//表示为以节点ID为键的哈希值:例如{node1:1,node2:3}
this.value =值|| {};
}

VectorClock.prototype.get = function(){
返回this.value;
};

VectorClock.prototype.increment = function(nodeId){
if(typeof this.value [nodeId] ==‘undefined’){
this.value [nodeId] = 1;
}其他{
this.value [nodeId] ++;
}
};

VectorClock.prototype.merge = function(other){
var result = {},最后,
a = this.value,
b =其他值;
//这会过滤掉哈希中的重复键
(对象键(a)
.concat(b))
。分类()
.filter(function(key){
var isDuplicate =(key == last);
last =键;
返回!isDuplicate;
})。forEach(function(key){
result [key] = Math.max(a [key] || 0,b [key] || 0);
});
this.value =结果;
};
此图(源)显示了一个矢量时钟:

来自http://en.wikipedia.org/wiki/Vector_clock

三个节点(A,B,C)中的每个节点都跟踪矢量时钟。当事件发生时,它们会用矢量时钟的当前值加上时间戳。检查诸如这样的矢量时钟{ A: 2, B: 4, C: 1 }可以让我们准确地识别(可能)影响该事件的消息。

向量时钟的问题主要是每个节点需要一个条目,这意味着它们对于大型系统可能会变得非常大。已经应用了各种技术来减小矢量时钟的大小(通过执行定期垃圾回收,或者通过限制大小来降低精度)。

我们已经研究了如何在没有物理时钟的情况下跟踪顺序和因果关系。现在,让我们看看如何将持续时间用于截止。

故障检测器(截止时间)
如前所述,等待所花费的时间可以提供有关系统是分区还是仅经历高延迟的线索。在这种情况下,我们不需要假设全局时钟具有完美的准确性-只要有一个足够可靠的本地时钟就足够了。

给定一个程序在一个节点上运行,它如何分辨一个远程节点发生了故障?在没有准确信息的情况下,我们可以推断出经过一段合理的时间后,无响应的远程节点已发生故障。

但是什么是“合理数额”?这取决于本地和远程节点之间的延迟。与其显式地指定具有特定值的算法(在某些情况下不可避免地会出错),不如处理一个合适的抽象。

故障检测器是一种抽象精确时序假设的方法。故障检测器使用心跳消息和计时器来实现。进程交换心跳消息。如果在超时发生之前未收到消息响应,则该进程将怀疑另一个进程。

基于超时的故障检测器将具有过于激进(声明节点发生故障)或过于保守(花费较长时间来检测崩溃)的风险。故障检测器需要多少精度才能使用?

Chandra等。(1996年)在解决共识的背景下讨论了故障检测器-这个问题特别相关,因为它是大多数复制问题的基础,在这种情况下,副本需要在具有延迟和网络分区的环境中达成共识。

它们使用两个属性(完整性和准确性)来表征故障检测器:

完整性强。
每个崩溃的进程最终都会被每个正确的进程怀疑。
完整性不足。
每个崩溃的进程最终都会被某个正确的进程怀疑。
准确性强。
从未怀疑有正确的过程。
准确性低。
从来没有怀疑过某些正确的过程。
完整性比准确性更容易实现;确实,所有重要的故障检测器都可以实现它-您要做的就是不要永远等到怀疑某人。Chandra等。请注意,可以将完整性较弱的故障检测器转换为具有较高完整性的故障检测器(通过广播有关可疑过程的信息),从而使我们可以专注于准确性属性的范围。

避免错误地怀疑非故障进程非常困难,除非您能够假定消息延迟存在最大的困难。该假设可以在同步系统模型中进行,因此故障检测器在这种系统中可以非常精确。在不对消息延迟强加任何限制的系统模型下,故障检测最多只能最终准确。

Chandra等。表明即使是非常弱的故障检测器-最终的弱故障检测器⋄W(最终弱精度+完整性较弱)也可以用来解决共识问题。下图(来自本文)说明了系统模型与问题可解决性之间的关系:

来自钱德拉和图埃格。 用于可靠的分布式系统的不可靠的故障检测器。 JACM 43(2):225-267,1996年。

如您在上面看到的,如果没有异步系统中的故障检测器,则无法解决某些问题。这是因为,如果没有故障检测器(或关于时间界限的强大假设,例如同步系统模型),就无法判断远程节点是否已崩溃,或者仅仅是经历了高延迟。对于任何旨在实现单副本一致性的系统而言,这种区别都是重要的:可以忽略失败的节点,因为它们不会导致发散,但是无法安全地忽略已分区的节点。

如何实现故障检测器?从概念上讲,简单的故障检测器没有什么用,它可以在超时到期时检测故障。最有趣的部分涉及如何判断远程节点是否发生故障。

理想情况下,我们希望故障检测器能够适应不断变化的网络状况,并避免将超时值硬编码到其中。例如,Cassandra使用应计故障检测器,它是输出可疑级别(0到1之间的值)而不是二进制“向上”或“向下”判断的故障检测器。这样,使用故障检测器的应用程序就可以在准确检测和早期检测之间做出权衡取舍。

时间,顺序和性能
早些时候,我提到必须为顺序付出代价。这是什么意思

如果你在写一个分布式系统,你可能拥有不止一台计算机。自然(和现实)的世界观是一个部分顺序,而不是一个全局顺序。您可以将部分顺序转换为全局顺序,但这需要通信、等待并施加限制,以限制在任何特定时间点可以工作的计算机数量。

所有的时钟仅仅是由网络延迟(逻辑时间)或物理限制的近似值。即使在多个节点上保持一个简单的整数计数器同步也是一个挑战。

虽然时间和顺序经常一起讨论,但时间本身并不是这样一个有用的属性。算法并不真正关心时间,而是关心更抽象的属性:

  • 事件的因果顺序
  • 失败检测(例如,消息传递上界的近似值)
  • 一致快照(例如,在某个时间点检查系统状态的能力;此处不讨论)

实行全局顺序是可能的,但代价高昂。它要求你以一般(最低)速度进行处理。通常,确保以某种定义的顺序传递事件的最简单方法是指定一个(瓶颈)节点,通过该节点传递所有操作。

时间/顺序/同步性真的有必要吗?这要看情况。在某些用例中,我们希望每个中间操作将系统从一个一致的状态移动到另一个一致的状态。例如,在许多情况下,我们希望来自数据库的响应表示所有可用的信息,并且希望避免处理系统可能返回不一致结果时出现的问题。

但在其他情况下,我们可能不需要那么多时间/顺序/同步。例如,如果您正在运行一个长时间运行的计算,并且直到最后才真正关心系统的工作,那么只要您能够保证答案是正确的,您就不需要太多的同步。

当只有一个子集的情况对最终结果有实际影响时,同步常常被用作所有操作的迟钝的工具(blunt tool)。什么时候需要顺序才能保证正确?在最后一章讨论的CALM定理提供了一个答案。

在其他情况下,给出一个只代表最优估计值的答案是可以接受的,也就是说,答案只基于系统中包含的全部信息的子集。尤其是,在网络分区期间,可能需要只访问系统的一部分来回答查询。在其他用例中,最终用户无法真正区分可以廉价获得的相对较新的答案和保证正确但计算昂贵的答案。例如,Twitter关注者是某个用户X的计数,还是X+1?或者电影A,B和C绝对是一些问题的最佳答案?做一个更便宜,最正确的“最大努力”是可以接受的。

在接下来的两章中,我们将研究容错强一致性系统的复制,这些系统提供了强大的保证,同时对故障的恢复能力也越来越强。这些系统为第一种情况提供了解决方案:当您需要保证正确性并愿意为此付出代价时。然后,我们将讨论具有弱一致性保证的系统,系统保证在分区面前仍然可用,但是只能给你一个“尽力”的答案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分布式系统是一种由多个独立的计算机节点组成的系统,这些节点通过网络互相通信和协调,共同完成任务。分布式系统的原则和范例可以总结如下。 首先,容错性是分布式系统的重要原则之一。由于分布式系统由多个节点组成,节点之间的通信可能会遇到错误、故障或延迟。为了提高系统的可靠性,分布式系统需要具备容错机制,能够自动检测和纠正错误,保证系统的正常运行。 其次,一致性是分布式系统的核心原则之一。分布式系统中的数据通常被存储在不同的节点上,这就带来了数据一致性的挑战。为了保证数据的一致性,分布式系统需要使用合适的一致性协议和算法,确保各个节点之间的数据一致性和同步。 同时,可扩展性也是分布式系统的重要原则之一。分布式系统需要具备良好的可扩展性,能够根据用户需求动态地扩展节点数量和处理能力。通过水平扩展和垂直扩展等手段,分布式系统可以实现高性能和高可用性。 此外,安全性也是分布式系统的原则之一。由于分布式系统中的节点和网络是开放的,容易受到安全攻击和数据泄露的威胁。为了保障系统的安全,分布式系统需要实现合适的安全机制,包括身份认证、数据加密和访问控制等。 最后,弹性性是分布式系统的原则之一。分布式系统需要具备弹性,能够在节点故障、网络拥塞或大量请求压力下保持正常运行。通过引入负载均衡、流量控制和自动伸缩等机制,分布式系统可以提高其弹性,并能够快速恢复正常运行。 总之,分布式系统的原则和范例包括容错性、一致性、可扩展性、安全性和弹性性。这些原则和范例的应用可以提高分布式系统的可靠性、性能和安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值