DDIA - 第8章 分布式系统的挑战

信息是激发创新的力量

        本章目标: 分布式系统的更多细节。

第8章 分布式系统的挑战

        所有可能出错的事情一定会出错。 关键在于,分布式系统与在单节点上的软件有着非常显著的区别,你会碰到五花八门、千奇百怪的问题所导致的各种故障。

        本章对分布式系统可能出现的故障做了一个全面、近乎悲观的总结。故障可能来自网络问题,以及时钟与时序问题等,并讨论这些问题的可控程度。

1 故障与部分失效

        单台节点上的软件通常不应该出现摸棱两可的现象(要么功能正常,要么完全失效) 然而当涉及多台节点时,情况发生了根本性变化。对于这种分布式系统,理想化的标准正确模型不在适用,我们必须面对一个可能非常混乱的现实。
        这是由于这种不确定性和部分失效大大提高了分布式系统的复杂性。

1.1 云计算和超算

        关于如何构建大规模计算系统的几种不同的思路(不同的集群构建方式针对不同的应用场景,同时所对应的错误处理方法也不尽相同):

  • 规模的一个极端是高性能计算(high-perfomance computing,HPC)。包含成千上万个CPU的超级计算机构成一个庞大的集群,通常用于计算密集型的科学计算任务,如天气预报或分子动力学(模拟原子和分子的运动)
  • 另一个极端是云计算。虽然云计算的定义并非那么明确,但通常它具有以下特征:多租户数据中心,通用计算机,用IP以太网链接,弹性/按需资源分配,并按需计费
  • 传统企业数据中心则位于以上两个极端之间

        我们需要在不可靠的组件之上构建可靠的系统。

2 不可靠的网络

        无共享并不是构建集群系统的唯一方式,但它却是构建互联网服务的主流方式。主要是由于以下几个原因:由于不需要专门的硬件因此成本相对低廉,可以采用通用的商品化硬件,可以采用跨区域的多数据中心来实现高可靠性。
        如果请求没有得到响应,则无法区分(a)请求丢失(b)远程节点关闭(c)响应丢失 而处理这个问题通常采用超时机制。

基于不可靠的组件构建可靠的系统

        在低可靠性部件上构建高可靠的系统一直是计算机领域的惯用手段,但可靠性总有其现实上限。例如:

  • 纠错码可以在各种通信链路上准确传输数据,包括那些可能偶尔传输出错的情况,例如无线网络出现信号干扰
  • IP(Internet协议)层本身并不可靠,可能会出现丢包、延迟、重复发送以及乱序等情况。但TCP(传输控制协议)在IP之上提供了更可靠的传输层,可以保证丢失的数据包被重传,消除重复包,报的顺序以发送的顺序重新组合等

2.1 现实中的网络故障

        网络分区:当网络的一部分由于网络故障而与其他部分断开,称之为网络分区或网络分割。
        处理网络故障并不意味着总是需要复杂的容错措施:假定你的网络通常非常可靠,而万一出现问题,一种简单的方法是对用户提示错误信息。

2.2 检测故障

        许多系统都需要自动检测节点失效的功能。总而言之,如果出现了问题,你可能会在应用堆栈的某个级别拿到了一个关于错误的回复,但最好假定最终收不到任何错误报告。接下来尝试重试(TCP重试是透明的,但也可以在应用级别重试),等待超时之后,如果还是没有收到响应,则最终声明节点已经失效。

2.3 超时与无限期的延迟

        如果超时是故障检测唯一可行的方法,那么超时应该设多长呢?不幸的是没有标准的答案。

2.3.1 网络拥塞与排队

        数据的整个生命周期都可能会存在排队。一个好的做法是,超时设置并不是一个不变的常量,而是持续测量响应时间及其变化(抖动),然后根据最新的响应时间分布来自动调整。TCP的重传超时也采用了类似的机制。

TCP与UDP

        这是一个可靠性与可变性之间的权衡考虑。

2.4 同步与异步网络

        如果网络层可以在规定的延迟内保证完成数据包的发送,且不会丢弃数据包,那么分布式系统就会简单很多。但事实上计算机网络很难实现像传统的固定电话网络的高可靠性和确定性。固定电话网络本质是同步的:即使数据中间经过了多个路由器,16bit空间在电路建立时已经在网络中得到预留,不会受到排队的影响。由于没有排队,网络最大的端到端延迟是固定的。我们称之为有界延迟。

延迟与资源利用率

        从更广泛的意义讲,可以将延迟的波动归为动态资源分区的结果。
        如果资源总是静态分配(例如,专门硬件和预留带宽分配),则某些环境下可以保证延迟的确定性。但是,这是以降低资源使用率为代价的,换句话说,其成本过于昂贵。而多租户、动态资源分配方式则可以提供更高的资源使用率,因而成本更低,当然也引入了可变延迟的缺点。
        简而言之,网络中的可变延迟并不是一种自然规律,只是成本与收益相互博弈的结果。

2.4.1 网络延迟是否可预测?

        假设数据中心网络和互联网基于电路交换网络,一旦电路建立完成则可以保证最大往返时间。然而,事实是以太网和IP都是基于分组交换协议,这种协议注定受到排队的影响,从而导致网络延迟不确定,在这些协议里完全没有电路的概念。

3 不可靠的时钟

        在分布式系统中,时间总是件棘手的问题,由于跨节点通信不可能即时完成,消息经由网络从一台机器到另一台机器总是需要花费时间。收到消息的时间应该晚于发送的时间,但是由于网络的不确定延迟,精确测量面临很多挑战。这些情况使得多节点通信时很难确定事情发生的顺序。每台机器的本地时间并非绝对准确,它们从其他更精确的服务器同步时间信息。

3.1 单调时钟与墙上时钟

        现代计算机内部至少有两种不同的时钟:墙上时钟(或称钟表时间),单调时钟。虽然它们都可以衡量时间,但要仔细区分二者,本质上他们时服务于不同的目的。

3.1.1 墙上时钟

        墙上时钟根据某个日历(也称为墙上时间)返回当前的日期与时间。例如,Linux的clock_gettime(CLOCK_REALTIME)和Java中的System.currentTimeMills()会返回自纪元1970年1月1日(UTC)以来的秒数和毫秒数,不含闰秒。而有些系统则适用其他日期其作为参考点。不适合测量时间间隔。

3.1.2 单调时钟

        单调时钟更适合测量持续时间段(时间间隔),例如超时或服务的响应时间:Linux上的clock_gettime(CLOCK_MONOTONIC)和Java中的System.nanoTime()返回的即是单调时钟。单调时钟的名字来源于它们保证总是向前(而不会出现墙上时钟的回拨现象)
        注意,单调时钟的绝对值并没有任何意义,它可能是电脑启动以后经历的纳秒数或者其他含义。因此比较不同节点上的单调时钟值毫无意义,它们没有任何相同的基准。
        在分布式系统中,可以采用单调时钟测量一段任务的持续时间(例如超时),它不假定节点间有任何的时钟同步,且可以容忍轻微测量误差。

3.2 时钟同步与准确性

        单调时钟不需要同步,但是墙上时钟需要根据NTP服务器或其他外部事件源做必要的调整。
        如果确实需要投入大量资源,是可以达到非常高的时钟精度。高精度的时钟可以采用GPS接收机,精确时间协议(PTP)并辅以细致的部署和监测。

3.3 依赖同步的时钟

        时钟虽然看起来简单,但却有不少使用上的陷阱:一天可能不总是86400秒,时钟会向后回拨,一个节点上的时间可能会与另一个节点上的时间完全不同。
        如果应用需要精确同步的时钟,最好仔细监控所有节点上的时钟偏差。如果某个节点的时钟漂移超出上限,应将其宣告为失效,并从集群中移除。这样的监控的目的是确保在造成重大影响之前今早发现并处理问题。

3.3.1 时间戳与事件顺序

        对于一个常见的功能:跨节点的事件排序,如果它高度依赖时钟计时,就存在一定的技术风险。
        多主节点复制的分布式数据库高度依赖于墙上时钟。写入被复制到其他节点时,会根据源写入节点上的墙上时钟来标记时间戳。基于时间戳的冲突解决策略被称为最后写入获胜(LWW),有些实现会在客户端生成时间戳而非服务器端,但无论如何没有改变LWW的根本问题:

  • 数据库写入可能会奇怪的丢失:明明后续发生的写操作却没法覆盖另一个较早的值,原因是后者节点的时钟太快了。这会导致一些数量未知的数据被悄悄低丢弃,并且不会向应用报告任何错误
  • LWW无法区分连续快速发生的连续写入操作(客户端A写入之后才发生了客户端B的增量操作)和并发写入(每个写操作都不依赖于其他写)。需要额外的因果关系跟踪机制(例如版本向量)来防止因果冲突
  • 由于时钟精度的限制(例如毫秒级),两个节点可能各自独立产生了完全相同的时间戳。为了解决这样的冲突,需要一个额外的仲裁值(可能简单地引入一个大的随机数),但该方法还是无法区分因果关系

        对于排序来说,基于递增计数器而不是震荡石英晶体的逻辑时钟是更可靠的方式。逻辑时钟并不测量一天的某个时间点或时间间隔,而是事务的相对顺序(事件发生的相对前后关系)。与之对应的,墙上时钟和单调时钟都属于物理时钟。

3.3.2 时钟的置信区间

        不应该将时钟读数视为一个精确的时间点,而更应该视为带有置信区间的时间范围。 可以根据具体的时间源来推算出时钟误差的上限。Google Spanner中的TrueTime API,它会明确地报告本地时钟的置信区间。当查询当前时间时,你会得到两个值:[不早于,不晚于]分别代表误差的最大偏差范围。基于上述两个时间戳,可以知道实际时间应在其范围之内。该间隔的范围主要取决于本地石英钟最后与高精度时钟同步后所经历的时间长短。

3.3.3 全局快照的同步时钟

        能否适用同步后的墙上时钟作为事务ID呢?问题的关键还是时钟精度的不确定性。
        Google Spanner采用了以下思路来实现跨数据中的快照隔离。他根据TrueTime API返回的时钟置信区间,并基于以下观察结果:如果有两个置信区间,每个置信区间都包含最早和最新可能的时间戳(A=[A-earliest,A-latest和B=[B-earliest,B-latest]),且这两个区间没有重叠(即A-earliest<A-latest<B-earliest<B-latest),那么可以断定B一定发生在A之后。只有发生了重叠,A和B发生顺序才无法明确。

3.4 进程暂停

        另一个分布式系统中危险使用时钟的例子:假设数据库每个分区只有一个主节点,只有主节点可以接收写入,那么其他节点该如何确信该主节点没有被宣告失败,可以安全地写入呢?租约机制,依赖同步时钟
        程序执行中会出现很多意外的暂停。 分布式系统中的一个节点必须假定,执行过程中的任何时刻都可能被暂停相当长一段时间,包括运行在某个函数中间。暂停期间,整个集群的其他部分都在照常运行,甚至会一致将暂停的节点宣告为故障节点。最终,暂停的节点可能会回来继续运行,除非再次检查时钟,否则它对刚刚过去的暂停毫无意义。

3.4.1 响应时间保证

        实时真的是实时吗? 在嵌入式系统中,实时通常意味着系统经过了精心设计和测试,以满足各种情况下执行时间约束。这与Web上模糊的“实时”术语有着鲜明的对比,后者主要描述了一种持续的流式处理方式,但并没有强的时间约束。
        “实时”与“高性能”不一样。 提供实时保证需要来自软件栈的多个层面的支持:首先是一个实时操作系统(real-time operating sysytem,RTOS),保证进程在给定的间隔内完成CPU时间片的调度分配;其次,库函数也必须考虑最坏的执行时间;然后,动态内存分配很可能要受限或者完全被禁止(如果存在实时垃圾收集器,确保GC不能处理太多任务);最终还是需要大量、充分的测试和验证,以确保满足要求。

3.4.2 调整垃圾回收的影响

        可以把GC暂停视为节点的一个计划内的临时离线或者只回收短期对象,对于长期对象进行定时重启。这些措施虽然并不能完全避免垃圾回收导致的进程暂停,但可以有效地减少对应用层的影响。

知识,真相与谎言

        目前为止,已经探索了分布式系统与单节点程序的许多不同之处。例如,很少使用共享内存,通过不可靠网络传递消息且延迟不确定,可能遭受部分失效,不可靠的时钟以及进程暂停等。
        即使底层模型仅提供了少数几个保证,也可以在系统软件层面实现可靠的行为保证。

真相由多数决定

        节点不能根据自身的信息来判断自身的状态。由于节点可能随时会失效,可能会暂停-假死,甚至最终无法恢复,因此,分布式系统不能完全依赖于单个节点。目前,许多分布式算法都依赖法定票数,即在节点之间进行投票。任何决策都需要来自多个节点的最小投票数,从而减少对特定节点的依赖。
        最常见的法定票数是取系统节点半数以上(也有其他类型的法定人数)。如果某些节点发生故障,quorum机制可以使系统继续工作。由于系统只可能存在一个多数,绝不会有两个多数在同时做出相互冲突的决定,因此系统的决议是可靠的。

主节点与锁

        很多情况,我们需要在系统范围内只能由一个实例。例如:

  • 只允许一个节点作为数据库分区的主节点,以防止脑裂
  • 只允许一个事务或客户端持有特定资源的锁,以防止同时写入从而导致数据破坏
  • 只允许一个用户来使用特定的用户名,从而确保用户名可以唯一标识用户

        分布式锁的不正确实现,会导致事实上已经过期的节点自认为有效,最终导致文件破坏。

Fencing令牌

        当使用锁和租约机制来保护资源的并发访问时,必须保证过期的“唯一的那个”节点不能影响其他正常部分。要实现这一目标,可以采用一种相当简单的技术fencing(栅栏,隔离之意
        我们假设每次锁服务在授予或租约时,还会同时返回一个fencing令牌,该令牌(数字)每授予一次就会递增(例如,由锁服务增加)。然后,要求客户端每次向存储系统发送写请求时,都必须包含所持有的fencing令牌。
        总之,为了避免在锁保护之外发生请求处理,需要进行额外的检查机制。 在服务器端检查令牌可能看起来有些复杂,但其实是推荐的正确做法:系统服务不能假定所有的客户端都表现符合预期,事实上客户端通常由权限级别较低的人来操作运行,因此存在一定的误用、滥用风险,从安全角度讲,服务端必须防范这种来自客户端的滥用。

拜占庭故障

        fencing令牌可以检测并组织那些无意的误操作(例如节点并没有发现其租约已经过期)。但是,如果节点试图故意试图破坏系统,在发送消息时可以简单地伪造令牌即可。如果节点存在“撒谎”的情况(即故意发送错误的或破坏性的响应),那么分布式系统处理的难度就上了一个台阶。例如,节点明明没有收到某条消息,但却对外声称收到了。这种行为被称为拜占庭故障,在这样不信任的环境中需要达成共识的问题也被称为拜占庭将军问题。
        如果某个系统中即使发生部分节点故障,甚至不遵从协议,或者恶意攻击、干扰网络,但仍可继续正常运行,那么我们称之为拜占庭容错系统。

拜占庭将军问题

        有n位将军需要达成共识,并且其中存在一些叛徒试图阻挠达成共识。大多数的将军都是忠诚的,发出了真实的信息,但是叛徒则试图通过发送虚假或不真实的信息来欺骗和混淆他人(同时努力隐藏自己)。而且大家事先并不知道叛徒是谁。

弱的谎言形式

        尽管我们假设节点通常是诚实的,但依然推荐增加必要的机制来防范一些不那么恶意的“谎言”。

理论系统模型与现实

        算法的实现不能过分依赖特定的硬件和软件配置。这就要求我们需要对预期的系统错误进行形式化描述。我们通过定义一些系统模型来形式化描述算法的前提条件。

        关于计时方面,有三种常见的系统模型:

  • 同步模型
            同步模型假定有上届的网络延迟,有上届的进程暂停和由上届的时钟误差
  • 部分同步模型
            部分同步意味着系统在大多数情况下像一个同步系统一样运行
  • 异步模型
            在这个模型中,一个算法不会对时机做任何的假设,甚至里面根本没有时钟(也就没有超时机制)

        除了时机之外,我们还需要考虑节点失效。有以下三种最常见的节点失效系统模型:

  • 崩溃-中止模型
            在崩溃-中止模型中,算法假设一个节点只能以一种方式发生故障,即遭遇系统崩溃。这意味着节点可能在任何时候突然停止响应,且该节点以后永远,无法恢复
  • 崩溃-恢复模型
            节点可能会在任何时候发生崩溃,且可能会在一段(未知的)时间之后得到恢复并再次响应。在崩溃-恢复模型中,节点上持久性存储(即非易失性存储)的数据会在崩溃之后得以保存,而内存中状态可能会丢失
  • 拜占庭(任意)失效模型
            如上节所述,节点可能发生任何事情,包括试图作弊和欺骗其他节点

        对于真实系统的建模,最普遍的组合是崩溃-恢复模型结合部分同步模型。那么接下来上层的分布式算法该如何应对这样的模型呢?

算法的正确性

        我们可以通过描述目标分布式算法的相关属性来定义其正确性。例如,对于锁服务的fencing令牌生成算法,要求算法具有以下属性:

  • 唯一性
            两个令牌请求不能获得相同的值
  • 单调递增
            如果请求x返回了令牌tx,请求y返回了令牌ty,且x在y开始之前先完成,那么tx<ty
  • 可用性
            请求令牌的节点如果不发生崩溃则最终能够一定会收到响应

        如果针对某个系统模型的算法在各种情况下都能满足定义好的属性要求,那么我们称这个算法是正确的。

安全与活性

        为进一步加深理解,有必要区分两种不同的属性:安全性活性。在上面的例子中,唯一性和单调递增属于安全属性,而可用性则属于活性。

  • 如果违反了安全属性,我们可以明确指向发生的特定时间点(例如,唯一性如果被违反,我们可以定位到具体哪个操作产生了重复零令牌)。且一旦违反安全属性,违规行为无法撤销,破坏已实际发生
  • 活性则反过来:可能无法明确某个具体的时间点(例如一个节点发送了一个请求,但还没有收到响应),但总是希望在未来某个时间点可以满足要求(即收到回复)
将系统模型映射到现实世界

        安全性、活性以及所建立的系统模型对于评测分布式算法的正确性意义重大。证明算法正确却并不意味着真实系统上的某个具体实现一定是正确的。

小结

        部分失效可能是分布式系统的关键特征。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值