『数据导向应用设计』第8章「分布式系统的问题」摘要
阅读《数据导向应用设计―可靠性、可扩展性、易维护性高的分布式系统设计原理》。
请注意
這是為我而撰寫的概述,其中有些資訊缺失。這篇文章並不適合當做資訊來源。
分散系统的问题在第8章讨论。
最終而言,作为工程师,我们的任务是构建能够完成工作的系统,即使一切都变得混乱(比如提供符合用户期望的保证)。
第8章中,对于之前乐观的策略,人们将以悲观的角度讨论能有多大程度上避免低可靠性的网络、低可靠性的时钟等问题。
8.1 故障与部分故障
在单个计算机上运行的软件可以很容易预测结果。它要么正常运行,要么无法运行。只要硬件正常运作,相同的处理总是会产生相同的结果(确定性)。
然而,在网络连接的多台计算机上运行的软件的行为很难预测。系统的一部分可能会损坏(部分故障)。此外,在执行涉及多个节点和网络的处理时,有时可能会正常工作,有时可能会出现意外的失败(非确定性)。由于传递成功消息的方式是通过网络,因此有时无法得知成功与否。
8.1.1 云计算与超级计算
在建立大规模计算系统时,有两种方法可供选择。
-
- スーパーコンピューターのような、ハイパフォーマンス・コンピューティング(HPC)
- 複数テナントのDCを接続したクラウドコンピューティング
在HPC领域中,我们可以将计算的中间结果进行检查点处理,并保存到存储中。如果在计算的过程中系统的某个部分出现故障,我们可以将系统崩溃然后从中间重新开始。这就类似于单节点计算机的容错处理。
云计算并非如此。
-
- オンラインでなければならない。クラッシュさせてサービスが止めることは受け入れられない
-
- 安く済むコモディティマシンを使うため障害の発生率は高くなる。スーパーコンピューターなどと違ってハードウェアの信頼性が高くない
-
- ネットワークトポロジーは高い2分割帯域幅を提供するためにClosトポロジーで構成されることが多い。スーパーコンピューターは、多次元メッシュやトーラスなどの特化したトポロジーを採用
-
- 規模が大きれば大きいほど、常に何かが故障している前提に立つ必要がある
-
- 障害を起こしたノードがあっても全体としては処理を継続できるなら運用やメンテナンスについて有益な機能。クラウド環境では、あるマシンのパフォーマンスが悪ければ、単純にそのマシンを破棄して新しいマシンを要求できる
- 地理的に分散している動作環境では、ローカルネットワークよりも信頼性の低いインターネットを経由することになる。スーパーコンピューターの場合、ノードはすぐ近くにある
在分散系统中认为故障很罕见是不明智的。考虑到可能发生的各种故障,包括几乎不可能发生的情况,是很重要的。在测试环境中人为地制造这些情况,观察会发生什么。
8.2 网络不可靠
大量的机器连接在一起的系统被称为共享计算系统,要访问机器特定的内存和硬件必须通过网络。
华盛顿特区的内部网络采用了异步数据包网络。节点可以向其他节点发送数据包,但网络不能保证数据包何时到达或者是否能到达。
-
- ネットワークケーブルが抜かれたかも
-
- ネットワークが輻輳していたり、リモートが過負荷になっていたりして、あとから配信されるかも
-
- リモートがクラッシュしているかも
-
- リモートが応答なし状態になっているかも(例:リモートがガベージコレクションによる高負荷)
-
- リモートのレスポンスがネットワーク上で消失したかも
- レスポンスが遅延配送されるかも
发送方无法知道数据包是否成功送达,也无法知道为何无法接收到响应。通常,这些情况会通过超时处理。
8.2.1 网络故障的实际情况
在一项关于中型数据中心的研究中发现,每月大约会发生12次网络故障。其中一半是由于单个机器断开连接,另一半是整个机架被切断。该研究表明,即使添加了冗余的网络设备,也不能保护免受人为错误这种导致故障的主要原因,并且故障并没有如预期那样减少。
众所周知,像EC2这样的公共云平台经常会出现临时网络故障。相比之下,良好管理的私人数据中心网络可以提供更稳定的环境。
即便在个人环境中网络故障很罕见,但仍然需要处理故障的发生。未预料到的情况下,软件会出现意料之外的行为。集群可能会陷入死锁,网络恢复后可能无法处理请求,或者可能会删除所有数据。
需要了解软件如何处理网络故障。为此,有一种手段是故意引发网络问题,测试系统的反应(混沌猴)
8.2.2 发现故障
-
- ロードバランサーは停止してしまったノードへのリクエスト送信を停止する(ローテから外す)
- シングルリーダーレプリケーションのシステムでは、リーダーに障害が発生したとき、フォロワーがリーダー昇格する
由于网络的不确定性,很难知道节点是否正常工作。
-
- ノードが動作しているはずのマシンに到達できるが、プロセスがない場合OSがTCP接続を拒否する。しかし、受信したリクエストを処理したあとにクラッシュしたら、ノードがどれだけのデータを処理したか知ることはできない
-
- ノードのプロセスがクラッシュしたものの、OSが動作しているなら、別ノードにクラッシュ通知を送り役割交代できる。HBaseはそういうことをやっている
-
- データセンターのネットワークスイッチの管理インタフェースにアクセスできるなら、ハードウェアレベルのリンク障害かどうか調べられる。スイッチにアクセスできない環境なら、これはできない
- 接続しようとしているIPアドレスが到達不能だとルーターが判断できるとき、ICMP Destination Unreachableパケットを返してくる場合がある
如果希望确认请求已成功,除了从应用程序本身接收适当的响应,没有其他选择。
8.2.3 无限制延迟和超时
检测故障的唯一可靠方法是超时。然而,超时的长度并没有简单的答案。
-
- タイムアウトが長ければ、ノードが落ちているとみなされる時間が伸びる。
- タイムアウトが短ければ、落ちていないノードを落ちているとみなすことが増える。
当将未掉线的节点视为已掉线时,可能会发生两次操作执行的情况。
想象一下拥有保证网络延迟上限的系统。在这个系统中,数据包可以在一定时间d内被交付或者丢失,同时请求的处理时间必须在一定时间r内完成。如果有这两个假设,那么超时时间可以确定为2d + r。然而,实际上并不存在同时满足这两个假设的系统,因此无法简单地进行思考。
8.2.3.1 网络拥塞和队列管理
在乘车出行时,所需的时间主要取决于交通拥堵的程度。计算机网络中的数据包延迟也是如此,主要是由排队引起的。
-
- 複数のノードが同時に同じ宛先にパケットを送るとき、スイッチはキューイングし、送信先のネットワークリンクに1つずつ送ります。ネットワークリンクが混雑しているとき、パケットはスロットを得るまで待ち時間があります(これはネットワークの輻輳と呼ばれます)。スイッチのキューを埋めてしまうような大量のデータがやってくると、パケットはドロップされ、パケットの再送が必要になります。
-
- パケットが宛先のマシンに到達しても、すべてのCPUがビジーなら、OSによってキューイングされます。キューが処理されるのは、マシンの負荷次第でどれくらいの時間がかかるか分からない。
-
- 仮想化された環境では、OSの動作は他の仮想マシンがCPUコアを使うときに、数十ミリ秒、一時的に停止する。この間VMはネットワークから来たデータを処理できないので、やってきたデータは仮想マシンのモニターによってキューイングされる。ネットワークの遅延の変動幅はさらに大きくなります。
-
- TCPはフロー制御(輻輳回避あるいはバックプレッシャーと呼ばれることもあります)を行います。その際に、ノードはネットワークリンクや受信側のノードを過負荷に陥らせることがないように、自身の送信レートを制限します。そのためデータはネットワークに入る前に送信側でもキューイングされます。
- TCPではラウンドトリップ時間から計算したタイムアウト時間でパケットがロスしたものと見なし、自動的に再送されます。アプリーケーションからは、パケットのロストと再送は区別できず、遅延という事実だけがわかります。
在公共云或多租户数据中心的情况下,资源是在多个客户之间共享的,像MapReduce这样的批处理工作负载可能会轻易使网络链接超负荷运行。如果附近的某个人正在使用大量资源,网络延迟可能会发生较大波动。在这种情况下,超时只能基于经验法则确定。
测量长时间和多台机器的往返时间分布,并确定延迟变化的期望值。然后考虑应用程序的性质,可以在检测故障所需的时间和超时时间过短的风险之间做出权衡判断。
不使用固定的的超时设置,而是持续测量响应时间及其变动,并根据观测到的响应时间分布自动调整超时时间也是不错的选择。这可以通过使用在Akka和Cassandra中使用的Phi Accrual故障检测器来实现。TCP的重传超时也是以类似的方式工作。
8.2.4 同步网络与异步网络 qí yǔ
如果网络延迟保持在固定的最大值以内,且不发生数据丢失,则分布式网络将变得简单得多。
传统的固定电话网络非常可靠,几乎不会出现语音帧延迟或通话中断的问题。电话交流具有低延迟且能传输人类语音采样所需的带宽。拨打电话后,电路会建立连接,并保证了一定数量的带宽在整个通话路径上。这个电路会一直保持直到通话结束。下一个网络跳跃的位置已经预先分配了16位的空间,因此不会受到排队的影响。
端到端的网络最大延迟也将保持不变。
8.2.4.1 可以简单地预测网络延迟吗?
电话线路在建立连接期间不可供他人使用。TCP连接的数据包将利用可用的带宽。
数据中心的网络和互联网是针对具有突发性流量进行优化的。如果需要的传输速度变化很大,那么分组交换方式更合适;但如果恒定的传输速度已经足够,那么电路交换更适合。如果使用电路交换来调整传输速度,就需要进行适当的分配,以避免过剩或不足。
8.3 低可靠性的时钟
时钟和时间都很重要。由于通信不是即时进行的,所以在消息到达时时间已经流逝。每台计算机都有自己的时钟。NTP(Network Time Protocol)就是一种常见的机制,用于将这些时钟进行一定程度的同步。
8.3.1 单调增加的时钟和时刻的时钟
现代电脑具有两种不同的时钟。一种是时钟,另一种是单调递增的时钟。
8.3.1.1 时间的时钟
时钟的作用是返回当前日期和时间,也被称为实时时间或墙上的时间。Linux的clock_gettime(CLOCK_REALTIME)返回的是自纪元以来经过的秒数。时钟会与NTP同步。因此,在同步时可能会跳回之前的时间,所以不适合用来测量消耗时间。
8.3.1.2 钟频单调增加
适用于测量时间限制、服务响应时间等。在Linux中,使用clock_gettime(CLOCK_MONOTONIC)可以实现这一点。它保证时钟始终向前运行。在具有多个CPU插槽的服务器上,每个CPU可能会有自己独立的计时器,因此不应该进行跨CPU的比较。
8.3.2 时间同步和准确性的时钟
时钟的同步需要设置NTP服务器或其他外部时间源。然而,同步存在一定的限制。
-
- クロックは温度によって変動します。1日1回の同期で17秒ずれます。
-
- クロックとNTPサーバーがずれすぎていると、同期が拒否されたり、時刻がリセットされます。アプリケーションから見ると時間がジャンプしているように見えます
-
- ファイアウォールの設定によってNTPサーバーと同期できない
-
- NTPとの同期で、ネットワークが輻輳していると、1秒近い誤差がでることがある
-
- NTPサーバーの時間が間違っている(数時間ずれている)ことがある
-
- NTPサーバーによってはうるう秒の調整を少しずつ行うサーバーがあるが、動作はそれぞれ
-
- クロックが仮想化されている仮想マシンの場合、ほかのVMが動作している間数十ミリ秒一時停止する。アプリケーションからみると時間をジャンプしているように見える
- 制御できないデバイス(携帯電話や組み込みデバイス)では、クロックは信頼できない。ハードウェアに手を入れてクロックをずらすユーザーもいる
在欧洲金融商品市场中,有一些要求与UTC进行100毫秒内的同步的产品,但是这需要大量的努力和专业知识(如PTP等)。
8.3.3 对于同步时钟的依赖
强健的软件需要对不准确的时钟进行准备。如果使用需要同步时钟的软件,则应仔细监测每台机器的时钟偏差。时钟偏差会导致数据丢失,不会引起剧烈崩溃但可能毫无察觉。如果偏差过大,则需要采取措施如将其从群集中排除。
8.3.3.1 具有顺序关系的时间戳
在涉及到多个节点的事件排序时,还需要注意到节点间的时钟差异。如果各个节点的时钟不同步,就可能导致意外的行为。

节点2错误地将x = 1识别为最新的写入,并丢弃了x = 2。
这种冲突避免策略被称为LWW(最后写入者胜出),在Cassandra和Riak等无主复制系统中使用。
通过保留“最近”值并丢弃其他值来解决冲突的方法存在不准确的情况,因为“最近”的定义依赖于本地时钟。
在事件排序方面,使用逻辑时钟是一个安全性较高的选择。请参阅第197页的5.4.4节”并行写入的检测”。
8.3.3.2 时钟值具有置信区间
机器的时钟具有微秒和纳秒级别的分辨率,但由于各种因素,与NTP服务器的同步通信等,可能会有10毫秒的误差。我们应该将读取的时钟视为时间的范围内,而不是单点的时刻。
大多数系统并未公开其误差范围。虽然GPS接收器和原子钟是不同的,但如果从服务器获得时间,我们无法知道误差的期望值。
谷歌的Spanner的TrueTime API是一个例外。它会明确报告本地时钟的可信区间。它会返回可能的最早时间和最迟时间的两个值。实际时间位于这个区间之间。
8.3.3.3 同步时钟用于全局快照
在实施快照隔离级别(7.2.2 快照隔离和可重复读)时,最常见的做法是需要生成递增的事务ID。在分散的节点上生成递增的ID将产生瓶颈。
Spanner 使用时刻钟的时间戳作为事务ID。只需等待时钟的可信区间,就可以明确因果关系。每个数据中心都部署原子时钟,将时钟同步误差控制在大约7毫秒内。
8.3.4 进程的暂停
在每个分区都有一个领导者的数据库中,某个节点想要确定自己是否继续担任领导者的一种方法是使用租约。租约是带有超时的锁,如果领导者在超时之前没有更新,锁将被解除并且另一个节点将成为领导者。如果某个节点错误地认为自己是领导者,将允许多次写入,这将导致数据不一致性的问题。
while (true) {
request = getIncomingRequest();
// リースが最低でも10秒残っていることを保証する
if (lease.expiryTimeMillis - System.currentTimeMillis() < 10000) {
lease = lease.renew()
}
if (lease.isValid()) { // この時点でプログラムが停止したら?
process(request); // ここの処理が長時間かかったら?
}
}
在判断自己是否是领导者时,要留意意外的临时中断。临时中断可能是由垃圾回收、磁盘访问、分页、活动迁移、交换到磁盘、SIGSTOP等原因引起的。
8.3.4.1 保证响应时间
许多系统无法确定线程或进程会在什么时候暂停。在航空器、火箭、机器人、汽车等硬实时系统中,软件有指定响应时间的截止日期。
开发实时系统有严格的约束,从进程调度到保证最坏情况执行时间的函数等。此外,会有吞吐量降低的可能性。(参见第311页的专栏”延迟和资源利用”)
8.3.4.2 通过垃圾收集限制影响
通过调整垃圾回收,可以缓解进程暂停的负面影响。当一个节点告诉另一个节点“GC可能很快就需要”时,可以设定策略,使其专注于GC并暂停发送新的请求。