分布式数据库区别于传统数据库的一个重要特性就是其分布式的特点,这些特点来源于分布式理论的发展,特别是数据分布相关理论的发展。相比于无状态分布式系统,有状态的数据库在分布式领域中将会面对更多的挑战。
失败模型
分布式系统是由多个节点参与其中的,它们直接通过网络进行互联。每个节点会保存本地的状态,通过网络来互相同步这些状态;同时节点需要访问时间组件来获取当前时间。对于分布式系统来说,时间分为逻辑时间与物理时间。逻辑时间一般被实现为一个单调递增的计数器,而物理时间对应的是一个真实世界的时间,一般由操作系统提供。
以上就是分布式系统所涉及的各种概念,看起很简单,实际上业界对分布式系统的共识就是上述所有环节没有一点是可靠的,“不可靠”贯穿了分布式系统的整个生命周期。而总结这些不可靠就成为失败模型所解决的问题。
在介绍失败模型的具体内容之前,让我们打开思路,看看有哪些具体的原因引起了分布式系统的可靠性问题。
引起失败的原因
当讨论分布式系统内的不稳定因素的时候,人们首先会想到网络问题,但是一个最容易让大家忽略的地方就是远程节点处理请求时也可能发生故障。一个比较常见的误区就是认为远程执行会马上返回结果,但这种假设是非常不可靠的。因为远程节点的处理能力、运行环境其实是未知的,我们不能认为它们会一直按照固定的模式去响应我们的请求。
而另一种情况是,请求到达远程节点后很可能不会被马上处理,而是放在了一个队列里面进行缓冲。这对于远程节点的吞吐量改善是有好处的,但是这在一定程度上带来了延迟,从而深刻地影响了交互模式。处理以上问题的方式就是需要引入故障检测(我会在下一讲介绍),来观察远程节点的运行情况,从而针对不同的问题采取不同的应对手段。
第二种常见的误解是所有节点时间是一致的,这种误解是非常普遍并且危险的。虽然可以使用工具去同步集群内的时间,但是要保持系统内时间一致是非常困难的。而如果我们使用不同节点产生的物理时间来进行一致性计算或排序,那么结果会非常不靠谱。所以大部分分布式数据库会用一个单独的节点来生成全局唯一的逻辑时间以解决上面的问题。而有些分布式数据库,如 Spanner 会使用原子钟这种精密服务来解决时间一致的问题。
本地物理时间的另一个问题是会产生回溯,也就是获取一个时间并执行若干步骤后,再去获取当前时间,而这个时间有可能比之前的时间还要早。也就是说我们不能认为系统的物理时间是单调递增的,这就是为什么要使用逻辑时间的另一个重要的原因。
但是本地物理时间在分布式系统中某些部分依然扮演着重要的作用,如判断远程节点超时等。但是基于以上两点,我们在实现分布式算法时应将时间因素考虑进去,从而避免潜在的问题。
以上谈到的分布式问题集中在节点层面,而另一大类问题就是网络造成的了。其中最为经典的问题就是网络分区,它指的是分布式系统的节点被网络故障分割为不同的小块。而最棘手的是,这些小块内的节点依然可以提供服务。但它们由于不能很好地感知彼此的存在,会产生不一致的问题,这个我们在模块一“05 | 一致性与 CAP 模型:为什么需要分布式一致性”有过比较详细的论述。
这里需要注意的是,网络分区带来的问题难以解决,因为它是非常难发现的。这是由于网络环境复杂的拓扑和参与者众多共同左右而导致的。故我们需要设计复杂的算法,并使用诸如混沌工程的方式来解决此类问题。
最后需要强调的一点是,一个单一读故障可能会引起大规模级联反映,从而放大故障的影响面,也就是著名的雪崩现象。这里你要注意,这种故障放大现象很可能来源于一个为了稳定系统而设计的机制。比如,当系统出现瓶颈后,一个新节点被加入进来,但它需要同步数据才能对外提供服务,而大规模同步数据很可能造成其他节点资源紧张,特别是网络带宽,从而导致整个系统都无法对外提供服务。
解决级联故障的方式有退避算法和断路。退避算法大量应用在 API 的设计中,由于上文提到远程节点会存在暂时性故障,故需要进行重试来使访问尽可能成功地完成。而频繁地重试会造成远程节点资源耗尽而崩溃,退避算法正是依靠客户端来保证服务端高可用的一种手段。而从服务端角度进行直接保护的方式就是断路,如果对服务端的访问超过阈值,那么系统会中断该服务的请求,从而缓解系统压力。
以上就是分布式系统比较常见的故障。虽然你可能会觉得这些故障很直观,但是如果要去解决它们思路会比较分散。还好前人已经帮我们总结了一些模型来对这些故障进行分级,从而有的放矢地解决这些问题。接下来我就要为你介绍三种典型的失败模型。
崩溃失败
当遭遇故障后,进程完全停止工作被称为崩溃失败。这是最简单的一种失败情况,同时结果也非常好预测。这种失败模式也称为崩溃停止失败,特别强调失败节点不需要再参与回分布式系统内部了。我们说这种模式是最容易预测的,是因为失败节点退出后,其他节点感知到之后可以继续提供服务,而不用考虑它重新回归所带来的复杂问题。
虽然失败停止模式有以上的优点,但实际的分布式系统很少会采用。因为它非常明显地会造成资源浪费,所以我们一般采用崩溃恢复模式,从而重复利用资源。提到崩溃节点恢复,一般都会想到将崩溃节点进行重启,而后经过一定的恢复步骤再加入网络中。虽然这是一种主流模式,但其实通过数据复制从而生成备份节点,而后进行快速热切换才是最为主流的模式。
崩溃失败可以被认为是遗漏失败的一种特殊情况。因为从其他节点看,他们很难分清一个节点服务响应是由于崩溃还是由于遗漏消息而产生的。那究竟什么是遗漏失败呢?
遗漏失败
遗漏失败相比于崩溃失败来说更为不可预测,这种模式强调的是消息有没有被远程节点所执行。
这其中的故障可能发生在:
1 消息发送后没有送达远程节点;
2 远程节点跳过消息的处理或根本无法执行(一种特例就是崩溃失败,节点无法处理消息);
3 后者处理的结果无法发送给其他节点。
总之,从其他节点的角度看,发送给该节点的消息石沉大海,没有任何响应了。
上文提到的网络分区是遗漏失败的典型案例,其中一部分节点间消息是能正常收发的,但是部分节点之间消息发送存在困难。而如果崩溃失败出现,集群中所有节点都将无法与其进行通讯。
另一种典型情况就是一个节点的处理速度远远慢于系统的平均水平,从而导致它的数据总是旧的,而此时它没有崩溃,依然会将这些旧数据发送给集群内的其他节点。
当远程节点遗漏消息时,我们是可以通过重发等可靠连接手段来缓解该问题的。但是如果最终还是无法将消息传递出去,同时当前节点依然在继续提供服务,那么此时遗漏失败才会产生。除了以上两种产生该失败的场景,遗漏失败还会发生在网络过载、消息队列满等场景中。
本文介绍了分布式系统中的失败模型,包括崩溃失败和遗漏失败。崩溃失败是节点完全停止工作,而遗漏失败关注的是消息是否被远程节点执行。网络问题、时间不一致性和级联故障是分布式系统不可靠的主要因素,需要通过故障检测和恢复策略来应对。

1957

被折叠的 条评论
为什么被折叠?



