Design Data-Intensive Applications 读书笔记二十三 第八章:故障和不可靠的网络
分布式带来的问题
之前的章节我们一直有讨论要怎么应对错误。例如我们讨论了故障转移,备份延迟,和事务的并发控制。我们看到了系统运营中的极端案例,以及要如何应对它们。但是现实是,我们还是过于乐观了。现实可能更糟糕。接下来我们会认为任何事情都会出错。
使用分布式系统与单主机的系统基本的不同就是:引入了很多新东西,而且会出现很多错误。下面我们就来看看实际中会出现的问题。最后作为工程师,我们就是要会发生错误的情况下建立稳健的系统。
这一章的视角全都是悲观的, 任何事情都会出错。我们会看到网络问题,时钟问题。然后我们会讨论在什么程度,它们可以避免。结论是这些议题都是混乱的,我们需要考虑如何思考分布式系统,如何归因问题。
故障和部分错误
当你在一台机器上编写程序时,行为是可以预期的,要么正确要么错误。单独主机上的程序毫无理由应当是明晰的:当硬件正确工作,相同的操作应当产生相同的结果(它是确定的)。如果有硬件问题,系统就会出故障。软件良好的一台机器要么完全有效,要么故障,不会有中间态。
在分布式系统中,系统的部分会以意想不到的方式挂掉,其他部分仍然正常工作。这就是局部故障。局部故障的难点是它是非确定性的。如果你在网络中使用多节点系统做事情,会时灵时不灵。你可能甚至不知道成功与否,因为网络传送信息的时间是非确定的。局部故障的非确定性和随机性使得分布式系统很难。
云计算和超级计算
建立大型计算系统的思想:
1、一个极端是高性能计算(HPC)。使用几千个CPU的超级计算机一般用于计算科研任务,例如天气预报或者分子动力学(模拟原子分子的运动)。
2、另一个极端是云计算,没有明确定义,但是一般与多数据中心,连接网络的商用计算机,弹性资源分配和定量账单相关。
3、传统的企业数据中心处于两者之间。
它们处理错误的思想不同。在超级计算中,工作是不时地记录检查点,如果一个节点故障,那么就会停止整个集群,在故障节点修复后,从上一个检查点重新开始。因此超级计算更加类似于单节点计算而不是分布式系统:它处理部分故障的方法是将其升级到整体故障,如果系统的任何部分失败,那么整体都停掉。
我们这本书里关注的网络服务系统与超级计算机不同:
1、很多网络相关的系统都是在线的,它们需要以低延迟提供服务。如果是停止集群来进行检修是不可取得。作为对比,类似于天气模拟的离线工作可以几乎无影响的停止然后重启。
2、超级计算机一般使用特定的硬件,每个节点都是可靠的,然后节点间使用共享内存和远程直接访问内存(RDMA). 另一方面,云服务的节点使用的是商业机器,性能与价格相关,但是有着高故障率。
3、大的数据中心的网络都使用IP和以太网,超级计算机经常使用特定的网络拓扑结构。
4、系统越大,组件出错的概率就越大。随着时间推移,故障的部分被修复,新的部件又出错,在一个有上千个节点的系统里,认为系统中总是有部分组件出错。如果简单放弃处理故障,那么系统会花很多时间来从故障中恢复,而不是提供有效的服务。
5、如果系统能容忍故障节点,并且像一个整体一样工作,对于操作和维护来说是好消息:例如你可以进行逐步升级,一次重启一个节点,同时服务依然运行。在云环境中,如果一台虚拟机器性能不好,你可以停掉它然后请求一台新的机器。
6、在地理意义上的分布式部署中,使用因特网来通信,对比本地网络很不可靠而且慢。超级计算机一般是将所有节点都聚集在一起。
如果你想构建分布式系统,需要接受局部故障和建立故障容忍机制。换句话说,需要使用不可靠的部件建立可靠的系统。就算是使用几个节点的小型系统也需要考虑局部故障。在小型系统里,大部分时间组件都是正常工作的。但是迟早系统的部分会停摆,软件不得不处理它。故障处理必须是软件的一部分,你需要知道发生故障时该如何处理。认为故障的概率低而不考虑故障是不明智的。需要认为大概率会发生故障然后在测试环境中人为地创造类似的环境才是合理的。在分布式系统中,怀疑,悲观和偏执才有效。
不可靠的网络
我们讨论的分布式系统时“无共享的系统”,只通过网络相连。
无共享不是构建系统的唯一的方式,但是这是目前的主流方式,多个原因:便宜,不需要额外的硬件;能利用云计算服务;部署多个后有高可靠性。
因特网和绝大多数数据中心的内部网络使用的是异步包网络。一个节点向另一个节点发送信息,但是没法保证信息什么时候发送,是否会送到。如果你发送信息后期望会得到回复,那么很多事情会出错。
发送者甚至没法分辨包是否被发送了,唯一的选择就是接收者发送回复信息,但是这也可能丢失。在异步网络中,这些都是没法分辨的,你唯一能得到的信息就是你没有收到回复。如果你发送请求而没有得到回复,没法分辨原因。最常见的处理方式是超时:一定时间后你放弃等待然后认为没有回复。但是超时发生时,你还是不知道远程节点有没有收到信息。
现实中的网络错误
现在已经有很多系统性研究,大量的证据显示网络问题及其普遍,即便是在一个公司内的数据中心。研究发现增加多余的网络装置不会按照预期的减少故障,因为它部分应对人造成的错误(例如,错误配置;人造成的错误时造成断开的主要原因)。
如果在你的环境中网络错误很少见,那么事实可能是你的软件能够处理它。任何时候网络上发生通信,它都可能失败,这无法避免。如果没法设计和测试如何处理网络故障,那么会发生糟糕的事情:例如,集群可能发生死锁并且长期不可用,即便是网络恢复了;或者是它会删除你所有的数据。如果软件在一个未知的环境,它也会产生意料之外的事情。
处理网络错误不仅意味着容忍它们:如果你的网络相当可靠,那么一个可用的方法可能就是简单的展示网络错误。但是如果你需要知道系统如何应对网络错误,需要确保系统能从错误中恢复。那么你需要故意触发网络错误然后测试系统的响应。
检测故障
很多系统需要自动检测故障节点,例如:
1、均衡负载需要停止向故障节点发送请求。
2、在单主节点备份的分布式数据库中,如果直接点挂掉,必须有一个从节点选举成为新的主节点。
但是网络的不确定性让辨别节点是否工作变得很难。在特定的场景中,你可能获得某些反馈准确告诉你有些事情出错了:
1、如果你访问一台机器的特定端口,这个节点应该正常工作,但是没有进程监听特定接口(进程挂掉),操作系统可能会发送RST或者FIN回复来关闭或者重用TCP连接。但是如果节点在处理请求的过程中挂掉,你没有方法知道节点上进程以及处理了多少数据。
2、如果节点上进程挂掉,但是节点的操作系统依然运行,可以用一个脚本告诉其他节点发生故障了,因而其他节点能快速应对,而不需要等到回应超时。HBase就是这么做的。
3、如果你有数据库的管理页面,那么你可以查询他们的硬件情况(有没有断电)。如果是通过网络访问,或者是处于共享数据库中但是没有方法访问本体,或者是因为网络问题没法访问管理页面,那么这种方法就无效了。
4、如果路由器可以确保一个ip无法访问,它可能回复 ICMP目的地无法到达。但是路由器也没有检测能力,它同样受制于网络上的其他部分。
频繁从远程端点哪里获得回馈是个有用的方法,但是你不能依赖它。即便是网络包送达到了,应用也可能故障而无法处理。如果你想确定请求是成功的,你需要从应用那儿获得正反馈。相反的,如果什么出错了,你可能会在多个层面上获得错误回复。一般而言,你必须认为你可能不会获得任何回复。你可以多试几次(必须在应用层面重试),等待超时,最后才可以认为节点故障了。
超时和无限延迟
如果超时是检测故障的唯一方法,那么多久才算超时?很不幸地,没有简单的答案。
超时时间长意味着需要长时间等待才能宣布节点故障(期间用户不得不等待或者看到错误信息)。超时时间短能更快地检测到故障,但是会带来风险:错误地判断节点故障,而实际上它知识短暂地停摆(例如因为负载达到峰值或者是网络问题)。
过早的宣称节点故障也是有问题的:如果节点实际上存活,仍在在执行动作,然后另一个节点替代了它,那么动作就会被执行两次。
当一个节点被宣布为故障时,它的工作需要被转移给其他节点,这会给其他节点和网络造成额外的负载。如果系统已经面临着高负载,那么宣布节点故障会让情况更糟糕。并且如果是节点没有故障只是因为过负载而回复慢,那么转移负载会造成级联错误(极端情况下,所有节点都会认为其他节点故障,整个系统停摆)。
设想一下,网络中一个机制能保证要么一个包的传输时间不会超过d,要么包丢失了。并且认为你保证节点处理请求的时间不会超过r。这种情况下你发出请求然后成功获得回复的时间不会超过2d+r,如果你在这段时间内没有获得回复,那么要么是网络问题,要么是远程节点没有工作。如果是这样,那么2d+r就是合理的超时时间。
但是不幸的,接大多数系统都没有提供这种保证:异步网络都是无限延迟,unbounded delays,即它们会尽快地发送包,但是对于到达的时间没有做限制。并且绝大多数服务的实现也没有保证能在规定时间内处理请求。对于故障检测,只是快是不够的,如果超时设置得低,几个来回就会导致峰值和系统的不均衡。
网络拥挤和排队
类似于堵车,网络中的包延迟大部分是因为排队的原因:包括网络层面,CPU,操作系统,TCP协议这些层面。
1、如果多个节点向相同目的地发送包,网络会将它们依次加入目的地的网络连接之中。在网络连接繁忙的情况下,包需要等待,这就是拥挤。如果队列中堆积了大量的数据,包就会被丢弃,需要重传,即便网络无问题。
2、当包到达了目的地,如果CPU很忙而来不及处理它,那么包就会被操作系统缓存起来等待应用处理。等待时间视机器的负载而定。
3、在虚拟机环境中,操作系统一般会在其他虚拟机使用CPU的时候等待,这时候没法处理网络传来的数据,所以传来的数据只能被虚拟机管理器缓存起来,这也会造成网络延迟。
4、TCP会进行流控制(拥塞避免或者背压),一个节点会限制发送至其他节点的速率来避免目标节点的过负载,也就是数据在进入网络前在发送器里也会排队。并且如果一定时间内数据没有得到回复,TCP会认为包丢失了,然后重传。尽管应用没有看到包丢失和重传,但是看到了延迟。
并且TCP认为一个包如果在一定时间内没有得到回复,则认为是丢包了,会进行重传;尽管应用没有看到丢包和重传,但是确实是延迟了。
以上这些就是造成延迟的因素。
在公共云或者是多用户系统中,用户共享资源:网络连接和开关网络接口和CPU。批处理工作会占用大量的网络链接。你没法控制其他用户占用共享资源。
因此只能根据经验确定超时时间,确定信号传遍整个网络的声音。然后考虑到应用的特点,你可以在错误检测延迟和过早过期之间做平衡。甚至可以不使用固定的超时时间,而是自动调整。使用 Phi Accrual错误检测器, Akka 和 Cassandra使用的。TCP重传超时机制也是类似的。
同步网络与异步网络
如果网络能保证延迟有上限,那么分布式系统就能简单很多,那么为什么不能从硬件角度让网络变得可靠呢?
我们可以对比数据中心网络和固定电话网络。电话网络很可靠:延迟和丢包率都很小。一个电话需要低延迟,点对点,和足够大的带宽来传输通话数据。这类似可靠,可预测的计算机网络。
但你使用固定电话的时候,会固定占用一定的带宽来建立通信回路。通话结束前都需要维持住回路。这种网络被称为同步网络:通过指定路径传输数据,而且对于通话数据,传输空间可足够了,所以不需要排队。
那么能否将网络延迟变得可预测?注意固定电话的回路需要占用带宽,这部分带宽其他任何人都不能使用。而TCP连接则会使用任何可能使用的带宽。你可以给TCP一个大小不定的数据块,它会尽可能快的发送数据,当TCP连接暂停时,它不会使用任何带宽。
如果数据中心切换网络会如何?首先以太网和ip协议都是包交换协议,需要排队的,协议没有回路这个概念。为什么使用包交换协议?因为它们对于突发性传输有优化。回路适用于通话数据,每秒传输固定字节的数据。但是,请求页面,发送邮件时没有特定的带宽需求,我们只是想更快的发送数据。
如果想使用回路发送数据,需要猜测要分配多少带宽。分配多了,其他回路的带宽就不够,分配少了,传输速度慢,而且其他未利用的带宽就闲置了,造成浪费。所以对于突发性传输,使用回路会造成浪费和低下的传输效率。作为对比,TCP会调整数据传输速度来利用网络。
也有人尝试建立混合网络来同时支持包交换和回路交换,例如ATM。无限带宽技术也有些类似:在链接层面实现了端对端的流控制,减少了排队,尽管因为链接拥挤,还是要面对延迟。使用服务质量( QoS,给包定优先级和日程)和接入(限制发送速率),可以在包交换网络上模拟回路交换,提供有限的延迟。
但是QoS在多用户数据中心和公共云上不可用。当前的部署技术也不会允许我们对网络的延迟和可靠性作出任何保障:我们不得不面对网络的拥挤,排队和无界延迟。结果是,“超时”没有准确答案,都是凭经验。