1 概念
1.1 模型
1.1.4 异常
分布式系统核心问题之一就是处理各种异常(failure)情况。本节着重讨论在本文范围内系统会遇 到的各种异常。
1.1.4.1 机器宕机
机器宕机是最常见的异常之一。在大型集群中每日宕机发生的概率为千分之一左右。在实践中,
一台宕机的机器恢复的时间通常认为是24 小时,一般需要人工介入重启机器。宕机造成的后果通常 为该机器上节点不能正常工作。假设节点的工作流程是三个独立原子的步骤,宕机造成的后果是节 点可能在处理完某个步骤后不再继续处理后续步骤。由于本文不考虑拜占庭问题,认为不会出现执 行完第一个步骤后跳过第二个步骤而执行第三个步骤的情况。宕机是一个完全随机的事件,在本文 中,认为在任何时刻都可能发生宕机,从而造成某些协议流程无法完成。
当发生宕机时,节点无法进入正常工作的状态,称之为“不可用”(unavailable)状态。机器重启 后,机器上的节点可以重新启动,但节点将失去所有的内存信息。在某些分布式系统中,节点可以 通过读取本地存储设备中的信息或通过读取其他节点数据的方式恢复内存信息,从而恢复到某一宕 机前的状态,进而重新进入正常工作状态、即“可用”(available)状态。而另一些分布式系统中的某 些无状态节点则无需读取读取任何信息就可以立刻重新“可用”。使得节点在宕机后从“不可用”到
“可用”的过程称为宕机恢复。
图 1-1 中虚线节点表示宕机的节点。
1.1.4.2 网络异常
网络异常是另一类常见的异常形式。在1.1.2 中已经定义,节点间通过不可靠的网络进行通信。
本节定义了本文范畴内的各种网络异常。
1.1.4.2.1 消息丢失
消息丢失是最常见的网络异常。对于常见的IP 网络来说,网络层不保证数据报文(IP fragment) 的可靠传递,在发生网络拥塞、路由变动、设备异常等情况时,都可能发生发送的数据丢失。由于 网络数据丢失的异常存在,直接决定了分布式系统的协议必须能处理网络数据丢失的情况。
依据网络质量的不同,网络消息丢失的概率也不同,甚至可能出现在一段时间内某些节点之间
的网络消息完全丢失的情况。如果某些节点的直接的网络通信正常或丢包率在合理范围内,而某些 节点之间始终无法正常通信,则称这种特殊的网络异常为“网络分化”(network partition)。网络分 化是一类常见的网络异常,尤其当分布式系统部署在多个机房之间时。图 1-1 中,用虚线分割了两 片节点,这两片节点之间彼此完全无法通信,即出现了“网络分化”。
例1.1:某分布式系统部署于两个机房,机房间使用内部独立光纤链路。由于机房间的光纤链路 交割调整,两个机房间通信中断,期间,各机房内的节点相互通信正常。更为严重的是,所有的英 特网用户都可以正常访问两个机房内对外服务节点。本文后续将讨论出现这种严重的网络分化时,
对分布式系统的设计带来的巨大挑战。
1.1.4.2.2 消息乱序
消息乱序是指节点发送的网络消息有一定的概率不是按照发送时的顺序依次到达目的节点。通 常由于IP 网络的存储转发机制、路由不确定性等问题,网络报文乱序也是一种常见的网络异常。这 就要求设计分布式协议时,考虑使用序列号等机制处理网络消息的乱序问题,使得无效的、过期的 网络消息不影响系统的正确性。
1.1.4.2.3 数据错误
网络上传输的数据有可能发生比特错误,从而造成数据错误。通常使用一定的校验码机制可以 较为简单的检查出网络数据的错误,从而丢弃错误的数据。
1.1.4.2.4 不可靠的 TCP
TCP 协议为应用层提供了可靠的、面向连接的传输服务。TCP 协议是最优秀的传输层协议之一,
其设计初衷就是在不可靠的网络之上建立可靠的传输服务。TCP 协议通过为传输的每一个字节设置 顺序递增的序列号,由接收方在收到数据后按序列号重组数据并发送确认信息,当发现数据包丢失 时,TCP 协议重传丢失的数据包,从而 TCP 协议解决了网络数据包丢失的问题和数据包乱序问题。
TCP 协议为每个 TCP 数据段(以太网上通常最大为 1460 字节)使用 32 位的校验和从而检查数据错 误问题。TCP 协议通过设置接收和发送窗口的机制极大的提高了传输性能,解决了网络传输的时延 与吞吐问题。TCP 协议最为复杂而巧妙的是其几十年来不断改进的拥塞控制算法,使得 TCP 可以动 态感知底层链路的带宽加以合理使用并与其他TCP 链接分享带宽(TCP friendly)。
上述种种使得TCP 协议成为一个在通常情况下非常可靠的协议,然而在分布式系统的协议设计 中不能认为所有网络通信都基于TCP 协议则通信就是可靠的。一方面,TCP 协议保证了 TCP 协议 栈之间的可靠的传输,但无法保证两个上层应用之间的可靠通信。通常的,当某个应用层程序通过 TCP 的系统调用发送一个网络消息时,即使 TCP 系统调用返回成功,也仅仅只能意味着该消息被本 机的TCP 协议栈接受,一般这个消息是被放入了 TCP 协议栈的缓冲区中。再退一步讲,即使目的 机器的TCP 协议栈后续也正常收到了该消息,并发送了确认数据包,也仅仅意味着消息达到了对方
机器的协议栈,而不能认为消息被目标应用程序进程接收到并正确处理了。当发送过程中出现宕机 等异常时,TCP 协议栈缓冲区中的消息有可能被丢失从而无法被目标节点正确处理。更有甚者,在 网络中断前,某数据包已经被目标进程正确处理,之后网络立刻中断,由于接收方的TCP 协议栈发 送的确认数据包始终被丢失,发送方的TCP 协议栈也有可能告知发送进程发送失败。另一方面,TCP 协议只能保证同一个TCP 链接内的网络消息不乱序,TCP 链接之间的网络消息顺序则无法保证。但 在分布式系统中,一个节点向另一个节点发送数据,有可能是先后使用多个TCP 链接发送,也有可 能是同时并发多个 TCP 链接发送,那么发送进程不能认为先调用 TCP 系统调用发送的消息就一定 会先于后发送的消息到达对方节点并被处理。
由上述分析,在设计分布系统的网络协议时即使使用TCP 协议,也依旧要考虑网络异常,不能 简单的认为使用TCP 协议后通信就是可靠的。另一方面,如果完全放弃使用 TCP 协议,使用 UDP 协议加自定义的传输控制机制,则会使得系统设计复杂。尤其是要设计、实现一个像TCP 那样优秀 的拥塞控制机制是非常困难的。
1.1.4.3 分布式系统的三态
由于网络异常的存在,分布式系统中请求结果存在“三态”的概念。在单机系统中,我们调用 一个函数实现一个功能,则这个函数要么成功、要么失败,只要不发生宕机其执行的结果是确定的。
然而在分布式系统中,如果某个节点向另一个节点发起RPC(Remote procedure call)调用,即某个节 点A 向另一个节点 B 发送一个消息,节点 B 根据收到的消息内容完成某些操作,并将操作的结果 通过另一个消息返回给节点A,那么这个 RPC 执行的结果有三种状态:“成功”、“失败”、“超时(未 知)”,称之为分布式系统的三态。
如果请求RPC 的节点 A 收到了执行 RPC 的节点 B 返回的消息,并且消息中说明执行成功,则 该RPC 的结果为“成功”。如果请求 RPC 的节点 A 收到了执行 RPC 的节点 B 返回的消息,并且消 息中说明执行失败,则该RPC 的结果为“失败”。但是,如果请求 RPC 的节点 A 在给定的时间内没 有收到执行RPC 的节点 B 返回的消息,则认为该操作“超时”。对于超时的请求,我们无法获知该 请求是否被节点B 成功执行了。这是因为,如果超时是由于节点 A 发向节点 B 的请求消息丢失造 成的,则该操作肯定没有被节点B 成功执行;但如果节点 A 成功的向节点 B 发送了请求消息,且 节点B 也成功的执行了该请求,但节点 B 发向节点 A 的结果消息被网络丢失了或者节点 B 在执行 完该操作后立刻宕机没有能够发出结果消息,从而造成从节点A 看来请求超时。所以一旦发生超时,
请求方是无法获知RPC 的执行结果的。图 1-2 给出了操作成功但超时的例子。
图 1-2RPC 执行成功但超时的例子
类的异常,需要在具体的项目中,通过长期的工程实践调整应对。
1.1.4.6 异常处理的原则
在设计、推导、验证分布式系统的协议、流程时,最重要的工作之一就是思考在执行流程的每 个步骤时一旦发生各种异常的情况下系统的处理方式及造成的影响。
例1.2:某分布式协议实现一个 echo 功能,即由节点 A 向节点 B 发送一个消息,内容是一个整 数,节点B 收到后返回相同的消息。节点 A 发送的消息每次递增加 1。
节点A 的处理流程为:
1. 向节点 B 发送一个消息,消息内容为当前需要发送的整数;
2. 等待接收从节点 B 发回的响应消息;
3. 若 B 发回的消息等于当前需要发送的整数,
a) 将当前需要发送的整数加 1;
b) 否则返回 1;
上述简单的流程可能遇到各种异常且不能正确处理:第一、当前需要发送的整数没有持久化,
在上述流程中,一旦节点A 宕机,节点 A 无法继续上述流程。第二、节点 B 一旦宕机,节点 A 不 会收到响应消息,流程将卡在第二步无法进行下去。第三、若A 发给 B 或 B 发回 A 的消息有一个 丢失,节点A 也不会收到响应消息。在本节中,不讨论如何修改这个流程以处理上述异常,举这个 例子是为了说明异常分析的基本方法。
在上述流程中,一旦节点A 宕机,节点 A 无法继续上述流程。第二、节点 B 一旦宕机,节点 A 不 会收到响应消息,流程将卡在第二步无法进行下去。第三、若A 发给 B 或 B 发回 A 的消息有一个 丢失,节点A 也不会收到响应消息。在本节中,不讨论如何修改这个流程以处理上述异常,举这个 例子是为了说明异常分析的基本方法。