Google File System 笔记
使用了单一主控方式,master仅储存文件的元数据,包括区块命名空间,文件和区块的映射,以及每个区块副本的位置,这些都直接存储在master的内存里,因此访问速度很快
操作日志很重要,是元数据的持久性记录,并且充当了定义并发操作顺序的逻辑时间线
GFS 的主要需求
在学习 GFS 的原理前,首先我们应当了解 GFS 在设计时所面对的需求场景。简单概括,GFS 的设计主要基于以下几个需求:
-
节点失效是常态。系统会构建在大量的普通机器上,这使得节点失效的可能性很高。因此,GFS 必须能有较高的容错性、能够持续地监控自身的状态,同时还要能够顺畅地从节点失效中快速恢复
-
存储内容以大文件为主。系统需要存储的内容在通常情况下由数量不多的大文件构成,每个文件通常有几百 MB 甚至是几 GB 的大小;系统应当支持小文件,但不需要为其做出优化
-
主要负载为大容量连续读、小容量随机读以及追加式的连续写
-
系统应当支持高效且原子的文件追加操作,源于在 Google 的情境中,这些文件多用于生产者-消费者模式或是多路归并
-
当需要做出取舍时,系统应选择高数据吞吐量而不是低延时
GFS 集群组成
简单来讲,除了客户端以外,一个 GFS 集群还包括一个 Master 节点和若干个 Chunk Server。它们会作为用户级进程运行在普通的 Linux 机器上。
在存储文件时,GFS 会把文件切分成若干个拥有固定长度的 Chunk(块)并存储。Master 在创建 Chunk 时会为它们赋予一个唯一的 64 位 Handle(句柄),并把它们移交给 Chunk Server,而 Chunk Server 则以普通文件的形式将每个 Chunk 存储在自己的本地磁盘上。为了确保 Chunk 的可用性,GFS 会把每个 Chunk 备份成若干个 Replica 分配到其他 Chunk Server 上。
GFS 的 Master 负责维护整个集群的元数据,包括集群的 Namespace(命名空间,即文件元数据)以及 Chunk Lease 管理、无用 Chunk 回收等系统级操作。Chunk Server 除了保存 Chunk 以外也会周期地和 Master 通过心跳信号进行通信,Master 也借此得以收集每个 Chunk Server 当前的状态,并向其发送指令。
鉴于整个集群只有一个 Master,客户端在和 GFS 集群通信时,首先会从 Master 处获取 GFS 的元数据,而实际文件的数据传输则会与 Chunk Server 直接进行,以避免 Master 成为整个系统的数据传输瓶颈;除此以外,客户端也会在一定时间内缓存 Master 返回的集群元数据。
GFS 的元数据
GFS 集群的所有元数据都会保存在 Master 的内存中。鉴于整个集群只会有一个 Master,这也使得元数据的管理变得更为简单。GFS 集群的元数据主要包括以下三类信息:
-
文件与 Chunk 的 Namespace
-
文件与 Chunk 之间的映射关系
-
每个 Chunk Replica 所在的位置
元数据保存在 Master 的内存中使得 Master 要对元数据做出变更变得极为容易;同时,这也使得 Master 能够更加高效地扫描集群的元数据,以唤起 Chunk 回收、Chunk 均衡等系统级管理操作。唯一的不足在于这使得整个集群所能拥有的 Chunk 数量受限于 Master 的内存大小,不过从论文的内容来看,这样的瓶颈在 Google 中从来没有被触及过,源于对于一个 64MB 大小的 Chunk,Master 只需要维持不到 64 字节的元数据。况且,相比于增加代码的复杂度,提高 Master 内存容量的成本要小得多。
为了保证元数据的可用性,Master 在对元数据做任何操作前对会用先写日志的形式将操作进行记录,日志写入完成后再进行实际操作,而这些日志也会被备份到多个机器上进行保存。不过,Chunk Replica 的位置不会被持久化到日志中,而是由 Master 在启动时询问各个 Chunk Server 其当前所有的 Replica。这可以省去 Master 与 Chunk Server 同步数据的成本,同时进一步简化 Master 日志持久化的工作。这样的设计也是合情合理的,毕竟 Chunk Server 当前实际持有哪些 Replica 也应由 Chunk Server 自己说了算。
数据一致性
用户在使用 GFS 这类数据存储系统时,首先应当了解其所能提供的数据一致性,而作为学习者我们也应先理解 GFS 对外呈现的数据一致性功能。
首先,命名空间完全由单节点 Master 管理在其内存中,这部分数据的修改可以通过让 Master 为其添加互斥锁来解决并发修改的问题,因此命名空间的数据修改是可以确保完全原子的。
文件的数据修改则相对复杂。在讲述接下来的内容前,首先我们先明确,在文件的某一部分被修改后,它可能进入以下三种状态的其中之一:
-
客户端读取不同的 Replica 时可能会读取到不同的内容,那这部分文件是不一致的(Inconsistent)
-
所有客户端无论读取哪个 Replica 都会读取到相同的内容,那这部分文件就是一致的(Consistent)
-
所有客户端都能看到上一次修改的所有完整内容,且这部分文件是一致的,那么我们说这部分文件是确定的(Defined)
在修改后,一个文件的当前状态将取决于此次修改的类型以及修改是否成功。具体来说:
-
如果一次写入操作成功且没有与其他并发的写入操作发生重叠,那这部分的文件是确定的(同时也是一致的)
-
如果有若干个写入操作并发地执行成功,那么这部分文件会是一致的但会是不确定的:在这种情况下,客户端所能看到的数据通常不能直接体现出其中的任何一次修改
-
失败的写入操作会让文件进入不一致的状态
GFS 支持的文件数据修改数据包括两种:指定偏移值的数据写入(Write)以及数据追加(Record Append)。当写入时,指定的数据会被直接写入到客户端指定的偏移位置中,覆盖原有的数据。GFS 并未为该操作提供太多的一致性保证:如果不同的客户端并发地写入同一块文件区域,操作完成后这块区域的数据可能由各次写入的数据碎片所组成,即进入不确定的状态。
与写入操作不同,GFS 确保即便是在并发时,数据追加操作也是原子且 at least once(至少一次)的。操作完成后,GFS 会把实际写入的偏移值返回给客户端,该偏移值即代表包含所写入数据的确定的文件区域的起始位置。由于数据追加操作是 at least once 的,GFS 有可能会在文件中写入填充(padding)或是重复数据,但出现的概率不高。
在读取数据时,为了避免读入填充数据或是损坏的数据,数据在写入前往往会放入一些如校验和等元信息以用于验证其可用性,如此一来 GFS 的客户端 library 便可以在读取时自动跳过填充和损坏的数据。不过,鉴于数据追加操作的 at lease once 特性,客户端仍有可能读入重复的数据,此时只能由上层应用通过鉴别记录的唯一 ID 等信息来过滤重复数据了。
读取文件
客户端从 GFS 集群中读取文件内容的过程大致如下:
-
对于指定的文件名和读取位置偏移值,客户端可以根据固定的 Chunk 大小来计算出该位置在该文件的哪一个 Chunk 中
-
客户端向 Master 发出请求,其中包含要读取的文件名以及 Chunk 索引值
-
Master 向客户端响应该 Chunk 的 Handle 以及其所有 Replica 当前所在的位置。客户端会以文件名和 Chunk 索引值为键缓存该数据
-
之后,客户端便可以选取其中一个 Replica 所在的 Chunk Server 并向其发起请求,请求中会指定需要读取的 Chunk 的 Handle 以及要读取的范围
对应用的影响
GFS 的一致性模型是相对松散的,这就要求上层应用在使用 GFS 时能够适应 GFS 所提供的一致性语义。简单来讲,上层应用可以通过两种方式来做到这一点:更多使用追加操作而不是覆写操作;写入包含校验信息的数据。
青睐追加操作而不是覆写操作的原因是明显的:GFS 针对追加操作做出了显著的优化,这使得这种数据写入方式的性能更高,而且也能提供更强的一致性语义。尽管如此,追加操作 at least once 的特性仍使得客户端可能读取到填充或是重复数据,这要求客户端能够容忍这部分无效数据。一种可行的做法是在写入的同时为所有记录写入各自的校验和,并在读取时进行校验,以剔除无效的数据;如果客户端无法容忍重复数据,客户端也可以在写入时为每条记录写入唯一的标识符,以便在读取时通过标识符去除重复的数据。
系统交互
用于减少在所有操作过程中master的参与
租赁机制和修改顺序控制流
修改就是改变一个区块内容或者元数据的操作,每个修改都要提现到该区块的所有副本中,我们使用租赁来保证副本间数据的一致性,master会给其中一个副本授权一个区块租约,该副本称作primary(主副本),其将自身选择的修改顺序应用至所有副本上
租赁机制旨在最大限度地减少主控服务器的管理开销,租赁机制的流程如下所示
-
客户端会向主控服务器(master)询问哪个 chunkserver 持有该块的当前租约,以及其他副本的位置。如果没有副本持有租约,主控服务器会将租约授予其选择的副本(图中未显示)。
-
主控服务器复制一份主副本的标识以及次级副本的位置信息,客户端会缓存这些数据以便未来的修改,只有当主副本不可访问或是副本不再持有租约时,才需要再次和主控服务器交互。
-
客户端可以以任意顺序向所有副本推送数据。每个区 chunkserver 会将这些数据储存到 LRU buffer中直到这些数据被使用或过期,通过解耦数据流和控制流,我们可以通过网络拓扑结构调度昂贵数据流来提升性能,无论哪个区 chunkserver 被指定为主副本
-
当所有副本全部确认接收到数据时,客户端向主副本发送一个写请求,该请求标明了先前推送到所有副本的数据。主副本会将一组连续的数字分配给可能来自多个客户端的修改操作,保证了必要的序列化。之后以序列数字顺序将修改应用至本地状态
-
主副本将写请求发送至所有次级副本,每个次级副本都会采用与主副本相同的序列化顺序
-
当完成所有操作时,次级副本向主副本发送通知
-
主副本回复客户端,在此期间任一副本发生的任一错误都会被报告给客户端,如果发生错误,写入操作可能在主副本和任意子集的次级副本上成功完成。客户端的请求会被认为发生错误,修改过的区域会一未同步的状态保留,客户端会尝试重试发生错误的修改,它将在步骤 (3) 到 (7) 中进行几次尝试,无果后回到写入的开始处重试。
如果应用程序的写入数据量很大或跨越了块边界,客户端会将本次写入切分为多个小体量的写入操作。当可能会与其它客户端的并发操作发生冲突并被覆盖,当共享的文件区域包含不同客户端的文件段时,该文件段会处于保证一致性但未定义的状态
数据流
我们通过控制流和数据流的解耦来更高效地使用网络,数据以流水线的方式在选定的区 chunkserver 链中线式(而非使用其他拓扑结构)推送。旨在完全利用每个机器服务器的带宽。在网络拓扑结构中,每个计算机会向没有接收到数据的最近计算机转发数据,以避免网络传输速率的瓶颈和高延迟连接。网络拓扑会被设计的十分简单,通过IP地址就可精确估算计算机间的距离。通过TCP协议流水线式传输数据以减少推送数据的延迟,全双工链路的交换网络可有效快速传输数据
原子性附加记录
GFS提供附加记录的原子操作,客户端指定数据,GFS 会将数据以原子方式(即,作为一个连续的字节序列)追加到文件至少一次,位置由 GFS 指定,并将该偏移量返回给客户端。并且在并发条件时不会产生数据竞争。如果客户端使用传统的写入方式,在大量使用记录追加功能的条件下需要额外实现复杂且成本昂贵的同步。对于前文提到的控制流来说,实现附加只需要主副本检查追加操作是否会超过单个数据块大小,如果超过,则将块填充至最大尺寸,通知次级副本执行相同的操作,回复客户端将要在下一个块再次执行该操作(记录附加大小被限制为 最多为最大块大小的四分之一,以将最坏情况下的碎片保持在可接受的水平)
如果在任一副本上发生错误,客户端会重试当前操作。GFS 不保证所有副本的字节顺序完全相同,只保证写入操作在原子单元下执行至少一次。要使操作报告成功,数据必须已在某个块的所有副本上以相同的偏移量写入。此外,在此之后,所有副本的长度至少与记录的末尾相同,因此,即使其他副本后来成为主副本,任何未来的记录都将被分配更高的偏移量或不同的块。就我们的一致性保证而言,成功的记录附加操作写入数据的区域是已定义的(因此是一致的),而中间的区域是不一致的(因此是未定义的)。
快照
快照操作可以在极短的时间内完成文件和目录树的复制,我们采用标准的写时复制(Copy-on-Write),多个对象共享同一份数据,直到某个对象尝试修改数据时,才真正执行复制操作。当主控服务器收到快照请求时,它首先会撤销即将快照的文件中所有未完成的块租约。这确保了任何后续对这些块的写入都需要与主控服务器交互以找到租约持有者,这将使主控服务器有机会首先创建该块的新副本。
当一个客户端在快照操作结束后第一次想要写入数据块C时,会像主控服务器发送请求以找到目前的租约持有者,主控服务器发现数据块C的引用数目大于一,便会推迟回复客户端并选择一个新的块。之后轮询每个数据 chunkserver ,找到存有C副本的数据 chunkserver 后创建一个新数据块C‘,在于C数据块相同的数据 chunkserver 当中创建新数据块可以保证数据的复制在本地执行,这要比网络传输来得更高效。从这点来看,对于任何块的请求处理都是差不多的
主控服务器操作
主控服务器会执行所有与命名空间有关的操作,并分配块副本,这包含储存管理,确保块复制能够完全执行,均衡 chunkserver 之间的负载以及回收未使用的储存空间
命名域管理与加锁
主控服务器的操作相当耗时,因此我们使用锁来实现主控服务器操作的并发。GFS不支持列出该目录下所有文件的预目录数据,也不支持相同文件或目录的别名。GFS 在逻辑上将其命名空间表示为一个将完整路径名映射到元数据的查找表。经过前缀压缩后,这个表可以在内存中被高效利用,在命名空间树状结构中的每个节点都预设一个读写锁。主控服务器操作在运行前要获得该节点至根节点路径上的每一把锁,以及完全路径下的读或写锁
锁定机制可以防止 在将 /home/user
快照保存到 /save/user
时创建文件 /home/user/foo
。快照操作会获取 /home
和 /save
的读锁,以及 /home/user
和 /save/user
的写锁。文件创建操作会获取 /home
和 /home/user
的读锁,以及 /home/user/foo
的写锁。这两个操作将被正确序列化,因为它们试图获取 /home/user
上的冲突锁。文件创建不需要获取父目录的写锁,因为没有“目录”,或者说,类似 inode 的数据结构需要保护以防止被修改。名称上的读锁足以保护父目录不被删除。
锁机制也支持在同一目录下的并发修改操作,对于目录的读锁会阻止对该目录的删除、快照、重命名操作,文件名上的写锁会序列化两次创建同名文件的尝试。
副本分配
不同机架上的计算机通信需要通过一个及以上的交换机。此外,进出机架的带宽可能小于机架内所有机器的总带宽,多级分布对于数据分布的可扩展性、可靠性和可用性(服务层面的持续提供能力)提出了较高的要求
块副本分配策略服务于两点原则:
-
最大化数据可靠性和可用性
-
最大化网络带宽利用率
我们还必须将块副本分配到不同的机架上。这确保即使整个机架损坏或离线(例如,由于网络交换机或电源电路等共享资源故障),仍能保存一些块的可用副本这也意味着,块的流量(尤其是读取流量)可以利用多个机架的总带宽。另一方面,写入流量必须流经多个机架,这是我们愿意做出的权衡。
创建、重新复制、重新均衡
在这三种情况下会创建块副本
主控服务器在放置初始空副本时,对于块的选择会考虑以下三点
-
我们希望将新的副本放置在磁盘空间利用率低于平均水平的 chunkserver 上,久而久之这会平均 chunkserver 间的硬盘利用率
-
我们希望限制每个 chunkserver 上最近创建任务的数量,创建任务本身的成本很小,但它可以可靠地预测即将发生的大量写入流量。因为块是在写入需要时创建的,并且在我们的一次追加多次读取的工作负载中,它们通常在完全写入后变为只读状态
-
我们希望在机架间传递副本
主控服务器会在可用副本数量低于用户指定的目标的情况下尽早开始重新复制,这包含 chunkserver 不可用、副本蒜黄,硬盘失效,副本预期上升等情况。每个块重新分配的优先级由以下几个因素决定:
-
现有副本数量距离复制目标数量的差距,差距越大优先级越高
-
首先重新复制活动文件的块,而不是最近删除的文件的块
-
阻塞客户端进程的块会被优先重新复制来减少运行应用程序的错误
每个 chunkserver 通过限制对源 chunkserver 的读取请求来限制其在每次克隆操作上花费的带宽量。
主控服务器会定期重新均衡副本,这会检查当前副本分布并移动副本以获得更好的磁盘空间和负载平衡,这会逐步填充新的数据块而非一次性大量写入导致大量写入流量,一般来说,它倾向于删除那些位于可用空间低于平均水平的 chunkserver 上的块,以平衡磁盘空间使用情况。
垃圾回收
清理残存文件和数据块,使系统更轻量更可靠
机制
在文件删除后,主控服务器的日志会立即记录删除操作,但是不会立即回收资源,文件只是被重命名为一个包含删除时间戳的隐藏名称。在主控服务器的周期性扫描中,任何留存超过三天(该时间间隔可自定义)的隐藏文件会被直接删除。在此之前,该文件仍可使用新的特殊名称读取,并可通过将其重命名为正常名称来恢复删除。当隐藏文件被删除时,其内存中的元数据也会被清除,这实际上切断了它与所有块的链接。
在类似的扫描中,无法被任何文件访问的孤儿数据块会被清除元数据,,每个 chunkserver 都会报告其拥有的块的子集, 然后主控服务器会回复所有不再存在于主控服务器元数据中的块的标识。 chunkserver 可以自由删除这些块的副本。
讨论
比起其他垃圾回收机制复杂的编码内容,我们的机制可以轻易标记数据块的所有引用,仅存在于主控服务器当中文件-数据块的映射中;我们也可以轻易标记所有数据块的副本,它们是每个 chunkserver 上指定目录下的Linux文件。任何主控服务器不知道的副本都是“垃圾”。
垃圾回收方法用于存储回收相比于立即删除,它具有诸多优势:
-
在常常出现错误的大规模分布式系统中更加简易并可靠,垃圾收集提供了一种统一且可靠的方法来清理任何未知有用的副本
-
将存储再声明整合入主控服务器的后台,因此,它是分批进行的,成本是摊销的,在主控服务器空闲是才会进行扫描,主控服务器可以更迅速地响应需要及时关注的客户请求。
-
延迟回收存储空间提供了一道安全网,防止意外、不可逆的删除
也有一些实际使用上的缺点,比如当存储空间紧张时,会妨碍用户调整使用方式;由应用程序重复创建删除的临时文件可能无法立即重用存储。我们通过在一个被删除的文件被显式地再次删除时,催促存储的回收来解决上述问题。我们还允许用户为命名空间的不同部分配置不同的复制,回收方法
过期副本检测
如果块服务器发生故障,块副本可能会过期并且丢失块的修改,对于每个块来说,主控服务器维护一个块版本号来区分最新更新的副本和过期副本
当主控服务器授权一个块新的租约时,该块的版本号会增加,并通知最新版的副本。主控服务器和这些副本会在持久化存储中记录新的版本号。由于客户端在更新完成后才会受到通知,因此能够避免与客户端的写操作发生冲突。如果有一个副本不可用,该副本的块版本号不会更新。当块服务器重启并且报告所管理的各个块集合及其版本号时,主控服务器会检测到块服务器中存在的过期副本。当主控服务器感知到比已记录数据更大的版本号时,会假设在授权租约时发生错误,因此会将较高的版本视为最新版本(采纳从分块服务器接收到的更高级的版本号)。
主控服务器在定期的垃圾处理中会删除过期副本。在这之前,它在回复客户端的区块信息请求时,实际上会认为过时的副本根本不存在。出于另一种安全考虑,当主控服务器通知客户端哪个块服务器持有某个块的租约时,或在执行克隆操作中指示一个块服务器读取另一个块服务器的区块时,会包含版本号。客户端或块服务器在执行操作时会验证这一版本号以确保总是可以访问最新的数据
容错和诊断
处理常态化的组件错误是个大麻烦,我们无法完全将系统托付给计算机和硬盘,依靠设计的容错机制和诊断工具可避免服务器宕机或数据损坏
高可用性
我们使用快速恢复和备份两个机制使整个系统维持高可用状态
快速恢复
无论主控服务器和块服务器正常停止运行或是异常停止运行,都能在几秒钟内恢复其状态并启动。服务端通过杀死进程定期重启,客户端和其他服务器会遇到轻微的故障,因为它们的未完成请求会超时,因此需要重新连接到重新启动的服务器并重试。
数据块备份
正如我们之前讨论的,每个数据块在不同机架上的多个服务器有多个备份,默认会维持三个备份。在校验和验证过程检测到损坏的副本,或在块服务器下线时,主控服务器会复制现有副本确保每个区块完全备份(备份数量达标)。尽管备份功能对我们很有帮助,我们仍在探索其他跨服务器冗余形式,例如奇偶校验或擦除码。以满足我们日益增长的只读储存需求。我们对文件的修改仅包含读和追加,因此有望在哦我们低耦合的系统中实现更加复杂的跨服务器冗余
主控服务器备份
基于可靠性的需求,主控服务器也会备份自身状态。其操作日志和存档会备份在多台机器上。对于主控服务器状态的修改仅在将所有备份的日志记录储存至硬盘后才可生效。为简单起见,仍保留一个主进程,控制后台活动和所有修改,例如系统内部进行修改的垃圾回收功能。当主控服务器停止运行时,几乎立刻就能重新启动。当硬件或硬盘出现问题时,GFS外的监控程序会在其他地方启动一个主控服务器,并复制操作日志。客户端仅使用主控服务器的规范名称(例如 gfs-test),这是一个 DNS 别名,如果主控服务器迁移到另一台机器,则可以更改该别名。
除此之外,“影子”主控服务器即使在主控服务器宕机时也能提供对文件系统的只读访问权限。这些是影子而非镜像,所以可能会与主控服务器有几分之一秒的轻微延迟,他们增强了未被经常操作的文件,或可容忍一些轻微过期数据应用程序的读操作的可用性。事实上在块服务器读取文件内容之前,应用程序不会发现这些过时的文件内容。在短期窗口中可能过时的就是文件元数据,包含目录内容和控制访问权限
为了保证影子主控服务器实时更新,它会读取主控服务器的操作日志,并完全复刻日志内的书顺序操作修改自己的数据结构。与主控服务器一样,它在启动时轮询块服务器(此后很少轮询)以定位块副本并与其频繁交换握手消息以监控其状态。它依赖主控服务器执行副本位置更新,而副本位置更新源于主控服务器创建和删除副本的决策。
数据完整性
每个块服务器采用校验和检查存储数据的损坏情况。在大规模的设备集群中经常出现硬盘故障导致在读或和写路径上造成数据损坏或丢失。我们可以使用别的区块备份来恢复数据,但通过比较块服务器间的备份来检测数据损坏是不现实的。除此之外,不同的备份可能是合法的,正如之前所说数据的追加不保证备份完全相同。因此每个块服务器必须通过维护校验和来独立验证其自身副本的完整性。
一个区块被分解为多个64KB的小块,每个小块有对应的32bits校验和。类似其他元数据,校验和独立于用户数据,保存在内存中并和日志记录一起持久化储存
对于读取来说,块服务器在向请求方(一个客户端或另一个块服务器)返回数据之前,要验证与读取范围重叠的数据块的校验和。因此块服务器不会向其他计算机设备发送损坏的信息。如果数据块与记录的校验和不匹配,块服务器会向请求方返回错误,并将该不匹配的情况报告主控服务器。作为回应,请求方会去读取另一个副本。新的有效副本到位后,主控服务器会指示该块服务器删除损坏副本
校验和在读性能方面的影响不大,体现在如下方面:
-
由于我们的大多数读取操作至少跨越几个块,因此我们只需要读取并校验相对少量的额外数据进行验证。
-
GFS 客户端代码 通过尝试在校验和块边界处对齐读取,进一步降低了此开销。
-
校验和查找,以及在块服务器上的比较无需任何 I/O 即可完成,并且校验和计算通常可以与 I/O 重叠
在块末尾处的数据追加写入校验和的计算被极大的优化,我们只是逐步增量更新最后一个部分校验和块的校验和,并为任何由追加操作填充的全新校验和块计算新的校验和。即使最后一个部分校验和块已经损坏,而我们现在无法检测到,新的校验和值也不会与存储的数据匹配,并且下次读取该块时,损坏情况将照常被检测到。
相反,如果写入操作覆盖了块的现有范围,则必须读取并验证被覆盖范围的第一个块和最后一个块,然后执行写入操作,最后计算并记录新的校验和。如果我们在部分覆盖前没有验证第一个和最后一个区块,新的校验和可能会隐藏未被覆盖区域中存在的损坏
在空闲期间,块服务器可以扫描并验证非活动块的内容。这使我们能够检测那些很少读取的块中的损坏情况。一旦检测到损坏,主控服务器就可以创建一个新的未损坏的副本,并删除损坏的副本。这可以防止非活动但损坏的块副本欺骗主控服务器,使其误以为它拥有足够多的块有效副本。
诊断工具
广泛而详细的诊断日志记录在问题隔离、调试和性能分析方面提供了巨大的帮助,同时成本却极低。如果没有日志,就很难理解机器之间瞬时且不可重复的交互。GFS 服务器会生成诊断日志,记录许多重要事件(例如,块服务器的启动和关闭)以及所有 RPC 请求和响应。这些诊断日志可以随意删除,而不会影响系统的正确性。但是,我们会在空间允许的范围内尽可能地保留这些日志。
RPC 日志包含网络上发送的确切请求和响应,但读取或写入的文件数据除外。通过将请求与响应进行匹配,并整理不同机器上的 RPC 记录,我们可以重建整个交互历史记录以诊断问题。这些日志还可以作为负载测试和性能分析的跟踪记录。
由于这些日志是顺序和异步写入的,因此日志记录对性能的影响极小(并且远远超过其带来的好处)。最新事件也会保存在内存中,可供持续在线监控。
附录
SOSP——操作系统顶会