转载请注明:http://duanple.blog.163.com/blog/static/7097176720109151211526/ 作者 phylips@bmy

3.系统交互

我们是以尽量最小化master在所有操作中的参与度来设计系统的。在这个背景下,我们现在描述下client,master以及chunkserver如何交互来实现数据变更,记录append以及快照的。 

3.1租约和变更顺序

一个变更是指一个改变chunk的内容或者元信息的操作,比如写操作或者append操作。每个变更都需要在所有的副本上执行。我们使用租约来保持多个副本间变更顺序的一致性。Master授权给其中的一个副本一个该chunk的租约,我们把它叫做主副本。这个主副本为针对该chunk的所有变更的选择一个执行顺序,然后所有的副本根据这个顺序执行变更。因此,全局的变更顺序首先是由master选择的租约授权顺序来确定的(可能有多个chunk需要进行修改),而同一个租约内的变更顺序则是由那个主副本来定义的。 

租约机制是为了最小化master的管理开销而设计的。一个租约有一个初始化为60s的超时时间设置。然而只要这个chunk正在变更,那个主副本就可以向master请求延长租约。这些请求和授权通常是与master和chunkserver间的心跳信息一起发送的。有时候master可能想在租约过期前撤销它(比如,master可能想使对一个正在重命名的文件的变更无效)。即使master无法与主副本进行通信,它也可以在旧的租约过期后安全的将租约授权给另一个新的副本。 

如图2,我们将用如下的数字标识的步骤来表示一个写操作的控制流程。

【google论文二】Google文件系统(中) - 星星 - 银河里的星星

 

1.client向master询问那个chunkserver获取了当前chunk的租约以及其他副本所在的位置。如果没有人得到租约,master将租约授权给它选择的一个副本。

2.master返回该主副本的标识符以及其他副本的位置。Client为未来的变更缓存这个数据。只有当主副本没有响应或者租约到期时它才需要与master联系。

3.client将数据推送给所有的副本,client可以以任意的顺序进行推送。每个chunkserver会将数据存放在内部的LRU buffer里,直到数据被使用或者过期。通过将控制流与数据流分离,我们可以通过将昂贵的数据流基于网络拓扑进行调度来提高性能,而不用考虑哪个chunkserver是主副本。3.2节更深入地讨论了这点。

4.一旦所有的副本接受到了数据,client发送一个写请求给主副本,这个请求标识了先前推送给所有副本的数据。主副本会给它收到的所有变更(可能来自多个client)安排一个连续的序列号来进行必需的串行化。它将这些变更根据序列号应用在本地副本上。

5.主副本将写请求发送给所有的次副本,每个次副本以与主副本相同的串行化顺序应用这些变更。

6.所有的次副本完成操作后向主副本返回应答

7.主副本向client返回应答。任何副本碰到的错误都会返回给client。出现错误时,该写操作可能已经在主副本以及一部分次副本上执行成功。(如果主副本失败,那么它不会安排一个序列号并且发送给其他人)。客户端请求将会被认为是失败的,被修改的区域将会处在非一致状态下。我们的客户端代码会通过重试变更来处理这样的错误。它会首先在3-7步骤间进行一些尝试后在重新从头重试这个写操作。

如果应用程序的一个写操作很大或者跨越了chunk的边界,GFS client代码会将它转化为多个写操作。它们都会遵循上面的控制流程,但是可能会被来自其他client的操作插入或者覆盖。因此共享的文件区域可能会包含来自不同client的片段,虽然这些副本是一致的,因为所有的操作都按照相同的顺序在所有副本上执行成功了。但是文件区域会处在一种一致但是未定义的状态,正如2.7节描述的那样。

 3.2数据流

为了更有效的使用网络我们将数据流和控制流分离。控制流从client到达主副本,然后到达其他的所有次副本,而数据则是线性地通过一个仔细选择的chunkserver链像流水线那样推送过去的。我们的目标是充分利用每个机器的网络带宽,避免网络瓶颈和高延时链路,最小化数据推送的延时。

 为了充分利用每个机器的网络带宽,数据通过chunkserver链线性的推送过去而不是以其他的拓扑进行分布比如树型。因此每个机器的带宽可以全部用来发送数据而不是为多个接受者进行切分。

 为了尽可能的避免网络瓶颈和高延时链路,每个机器向网络中还没有收到该数据的最近的那个机器推送数据。假设client将数据推送给S1- S4,它会首先将数据推送给最近的chunkserver假设是S1,S1推送给最近的,假设S2,S2推送给S3,S4中离他最近的那个。我们网络拓扑足够简单,以至于距离可以通过IP地址估计出来。

 最后为了最小化延时,我们通过将TCP数据传输进行流水化。一旦一个chunkserver收到数据,它就开始立即往下发送数据。流水线对我们来说尤其有用,因为我们使用了一个全双工链路的交换网络。立即发送数据并不会降低数据接受速率。如果没有网络拥塞,向R个副本传输B字节的数据理想的时间耗费是B/T+RL,T代表网络吞吐率,L是机器间的网络延时。我们的网络连接是100Mbps(T),L远远低于1ms,因此1MB的数据理想情况下需要80ms就可以完成。

3.3原子性的记录append

GFS提供一个原子性的append操作叫做record append(注意这与传统的append操作也是不同的)。在传统的写操作中,用户指定数据需要写的便宜位置。对于相同区域的并行写操作是不可串行的:该区域的末尾可能包含来自多个client的数据片段。但在一个record append操作中,client唯一需要说明的只有数据。GFS会将它至少原子性地append到文件中一次,append的位置是由GFS选定的,同时会将这个位置返回给client。这很类似于unix文件打开模式中的O_APPEND,当多个写者并发操作时不会产生竞争条件。

 Record append在我们的分布式应用中被大量的使用。很多在不同机器的client并发地向同一个文件append。如果使用传统的写操作,client将需要进行复杂而又昂贵的同步化操作,比如通过一个分布式锁管理器。在我们的工作负载中,这样的文件通常作为一个多生产者/单消费者队列或者用来保存来自多个不同client的归并结果。

 Record append是一种类型的变更操作,除了一点在主副本上的额外的逻辑外依然遵循3.1节的控制流。Client将所有的数据推送给所有副本后,它向主副本发送请求。主副本检查将该记录append到该chunk是否会导致该chunk超过它的最大值(64MB)。如果超过了,它就将该chunk填充到最大值,告诉次副本做同样的工作,然后告诉客户端该操作应该在下一个trunk上重试。(append的Record大小需要控制在最大trunk大小的四分之一以内,这样可以保证最坏情况下的碎片可以保持在一个可以接受的水平上 )。如果记录可以没有超过最大尺寸,就按照普通情况处理,主副本将数据append到它的副本上,告诉次副本将数据写在相同的偏移位置上,最后向client返回成功应答。

 如果record append在任何一个副本上失败,client就会重试这个操作。这样,相同chunk的多个副本就可能包含不同的数据,这些数据可能包含了相同记录的整个或者部分的重复值。GFS并不保证所有的副本在位级别上的一致性,它只保证数据作为一个原子单元最少写入一次。这个属性是由如下的简单观察推导出来的,当操作报告成功时,数据肯定被写入到某个trunk的所有副本的相同偏移位置上。此后,所有的副本至少达到了记录尾部的大小,因此未来的记录将会被放置在更高的便宜位置,或者是另一个不同的chunk,即使另一个副本变成了主副本。在我们的一致性保证里,record append操作成功后写下的数据区域是已定义的(肯定是一致的),然而介于其间的数据则是不一致的(因此也是未定义的)。我们的应用程序可以处理这样的不一致区域,正如我们在2.7.2里讨论的那样。

 3.4快照

快照操作可以非常快速的保存文件或者目录树的一个拷贝,同时可以最小化对于正在执行的变更操作的中断。用户经常用它来创建大数据集的分支拷贝,以及拷贝的拷贝……。或者用来创建检查点,以实验将要提交的拷贝或者回滚到更早的状态。

 像AFS,我们使用标准的写时拷贝技术来实现快照。当master收到一个快照请求时,它首先撤销将要进行快照的那些文件对应的chunk的所有已发出的租约。这就使得对于这些chunk的后续写操作需要与master交互来得到租约持有者。这就首先给master一个机会创建该chunk的新的拷贝。

 当这些租约被撤销或者过期后,master将这些操作以日志形式写入磁盘。然后复制该文件或者目录树的元数据,然后将这些日志记录应用到内存中的复制后的状态上,新创建的快照文件与源文件一样指向相同的chunk。

 当client在快照生效后第一次对一个chunk C进行写入时,它会发送请求给master找到当前租约拥有者。Master注意到对于chunk C的引用计数大于1。它延迟回复客户端的请求,选择一个新的chunk handle C`。然后让每个拥有C的那些chunkserver创建一个新的叫做C`的chunk。通过在相同的chunkserver上根据原始的chunk创建新chunk,就保证了数据拷贝是本地地,而不是通过网络(我们的硬盘比100Mbps网络快大概三倍)。这样,对于任何chunk的请求处理都没有什么不同:master为新才chunk C`的副本中的一个授权租约,然后返回给client,这样它就可以正常的写这个chunk了,client不需要知道该chunk实际上是从一个现有的chunk创建出来的。

 4.master操作

Master执行所有的名字空间操作。此外,它还管理整个系统的chunk备份:决定如何放置,创建新的chunk和相应的副本,协调整个系统的活动保证chunk都是完整备份的,在chunkserver间进行负载平衡,回收没有使用的存储空间。我们现在讨论这些主题。

 4.1名字空间管理和锁

很多master操作都需要花费很长时间:比如,一个快照操作要撤销该快照所包含的chunk的所有租约。我们并不想耽误其他运行中的master操作,因此我们允许多个操作同时是活动的,通过在名字空间区域使用锁来保证正确的串行化。

  不像传统的文件系统,GFS的目录并没有一种数据结构用来列出该目录下所有文件,而且也不支持文件或者目录别名(像unix的硬链接或者软连接那样)。GFS在逻辑上通过一个路径全称到元数据映射的查找表来表示它的名字空间。通过采用前缀压缩,这个表可以有效地在内存中表示。名字空间树中的每个节点(要么是文件的绝对路径名称要么是目录的)具有一个相关联的读写锁。

 每个master操作在它运行前,需要获得一个锁的集合。比如如果它想操作/d1/d2…/dn/leaf,那么它需要获得/d1,/d1/d2……/d1/d2…/dn这些目录的读锁,然后才能得到路径/d1/d2…/dn/leaf的读锁或者写锁。Leaf可能是个文件或者目录,这取决于具体的操作。

 我们现在解释一下,当为/home/user创建快照/save/user时,锁机制如何防止文件/home/user/foo被创建。快照操作需要获得在/home /save上的读锁,以及/home/user和/save/user上的写锁。文件创建需要获得在/home和/home/user上的读锁,以及在/home/user/foo上的写锁。这两个操作将会被正确的串行化,因为它们试图获取在/home/user上的相冲突的锁。文件创建并不需要父目录的写锁,因为实际上这里并没有”目录”或者说是类似于inode的数据结构,需要防止被修改。读锁已经足够用来防止父目录被删除。

这种锁模式的一个好处就是它允许对相同目录的并发变更操作。比如多个文件的创建可以在相同目录下并发创建:每个获得该目录的一个读锁,以及文件的一个写锁。目录名称上的读锁足够可以防止目录被删除,重命名或者快照。文件名称上的写锁将会保证重复创建相同名称的文件的操作只会被执行一次。

因为名字空间有很多节点,所以读写锁对象只有在需要时才会被分配,一旦不再使用用就删除。为了避免死锁,锁是按照一个一致的全序关系进行获取的:首先根据所处的名字空间树的级别,相同级别的则根据字典序。

 4.2 备份放置

GFS在多个层次上都具有高度的分布式。它拥有数百个散步在多个机柜中的chunkserver。这些chunkserver又可以被来自不同或者相同机柜上的client访问。处在不同机柜的机器间的通信可能需要穿过一个或者更多的网络交换机。此外,进出一个机柜的带宽可能会小于机柜内所有机器的带宽总和。多级的分布式带来了数据分布式时的扩展性,可靠性和可用性方面的挑战。

 Chunk的备份放置策略服务于两个目的:最大化数据可靠性和可用性,最小化网络带宽的使用。为了达到这两个目的,仅仅将备份放在不同的机器是不够的,这只能应对机器或者硬盘失败,以及最大化利用每台机器的带宽。我们必须在机柜间存放备份。这样能够保证当一个机柜整个损坏或者离线(比如网络交换机故障或者电路出问题)时,该chunk的存放在其他机柜的某些副本仍然是可用的。这也意味着对于一个chunk的流量,尤其是读取操作可以充分利用多个机柜的带宽。另一方面,写操作需要在多个机柜间进行,但这是我们可以接受的。

 4.3创建 重备份 重平衡

Chunk副本的创建主要有三个原因:chunk的创建,重备份,重平衡。

 当master创建一个chunk时,它将初始化的空的副本放置在何处。它会考虑几个因素:1.尽量把新的chunk放在那些低于平均磁盘空间使用值的那些chunkserver上。随着时间的推移,这会使得chunkserver的磁盘使用趋于相同2.尽量限制每个chunkserver上的最近的文件创建数,虽然创建操作是很简单的,但是它后面往往跟着繁重的写操作,因为chunk的创建通常是因为写者的需要而创建它。在我们的一次append多次读的工作负载类型中,一旦写入完成,它们就会变成只读的。3.正如前面讨论的,我们希望在机柜间存放chunk的副本

 当chunk的可用备份数低于用户设定的目标值时,Master会进行重复制。有多个可能的原因导致它的发生:chunkserver不可用,chunkserver报告它的某个备份已被污染,一块硬盘由于错误而不可用或者用户设定的目标值变大了。需要重复制的chunk根据几个因素确定优先级。一个因素是它与备份数的目标值差了多少,比如我们给那些丢失了2个副本的chunk比丢失了1个的更高的优先级。另外,比起最近被删除的文件的chunk,我们更想备份那些仍然存在的文件的chunk(参考4.4节)。最后了,为了最小化失败对于运行中的应用程序的影响,我们提高那些阻塞了用户进度的chunk的优先级。

 Master选择最高优先级的chunk,通过给某个chunkserver发送指令告诉它直接从一个现有合法部分中拷贝数据来进行克隆。新备份的放置与创建具有类似的目标:平均磁盘使用,限制在单个chunkserver上进行的clone操作数,使副本存放在不同机柜间。为了防止clone的流量淹没client的流量,master限制整个集群已经每个chunkserver上处在活动状态的clone操作数。另外每个chunkserver还会限制它用在clone操作上的带宽,通过控制它对源chunkserver的读请求。

最后,master会周期性的对副本进行重平衡。它检查当前的副本分布,然后为了更好的磁盘空间使用和负载瓶颈,将副本进行移动。而且在这个过程中,master是逐步填充一个新的chunkserver,而不是立即将新的chunk以及大量沉重的写流量使他忙的不可开交。对于一个新副本的放置,类似于前面的那些讨论。另外,master必须选择删除哪个现有的副本。通常来说,它更喜欢那些存放在低于平均磁盘空闲率的chunkserver上的chunk,这样可以使磁盘使用趋于相等。

 4.4垃圾回收

文件删除后,GFS并不立即释放可用的物理存储。它会将这项工作推迟到文件和chunk级别的垃圾回收时做。我们发现,这种方法使得系统更简单更可靠。

 4.4.1机制

当文件被应用程序删除时,master会将这个删除操作像其他变化一样理解写入日志。文件不会被立即删除,而是被重命名为一个包含删除时间戳的隐藏名称。在master对文件系统进行常规扫描时,它会删除那些存在时间超过3天(这个时间是可以配置的)的隐藏文件。在此之前,文件依然可以用那个新的特殊名称进行读取,或者重命名回原来的名称来取消删除。当隐藏文件从名字空间删除后,它的元数据会被擦除。这样就有效地切断了它与所有chunk的关联。

 在chunk的类似的常规扫描中,master找到那些孤儿块(无法从任何文件到达),擦除这些块的元数据。在与master周期性交互的心跳信息中,chunkserver报告它所拥有的chunk的那个子集,然后master返回那些不在master的元数据中出现的chunk的标识。Chunkserver就可以自由的删除这些chunk的那些副本了。

 4.4.2讨论

尽管程序设计语言中的分布式垃圾回收是一个需要复杂解决方案的难解问题,但是在我们这里它是很简单的。我们可以简单的找到对于chunk的所有引用:因为它们保存在只由master维护的一个文件-chunk映射里。我们可以找到所有chunk的副本:它们不过是存放在每个chunkserver的特定目录下的linux文件。任何master不知道的副本就是垃圾。

 采用垃圾回收方法收回存储空间与直接删除相比,提供了几个优势:1.在经常出现组件失败的大规模分布式系统中,它是简单而且可靠的。Chunk创建可能在某些chunkserver上成功,在另外一些失败,这样就留下一些master所不知道的副本。副本删除消息可能丢失,master必须记得在出现失败时进行重发。垃圾回收提供了一种同一的可信赖的清除无用副本的方式。2.它将存储空间回收与master常规的后台活动结合在一起,比如名字空间扫描,与chunkserver的握手。因此它们是绑在一块执行的,这样开销会被平摊。而且只有当master相对空闲时才会执行。Master就可以为那些具有时间敏感性的客户端请求提供更好的响应。3.空间回收的延迟为意外的不可逆转的删除提供了一道保护网。

 根据我们的经验,主要的缺点是,当磁盘空间很紧张时,这种延时会妨碍到用户对磁盘使用的调整。那些频繁创建和删除中间文件的应用程序不能够立即重用磁盘空间。我们通过当已删除的文件被再次删除时加速它的存储回收来解决这个问题。我们也允许用户在不同的名字空间内使用不同的重备份和回收策略。比如用户可以指定某个目录树下的文件的chunk使用无副本存储,任何已经删除的文件会被立即删除并且从当前文件系统中彻底删除。

 4.5过期副本检测

如果chunkserver失败或者在它停机期间丢失了某些更新,chunk副本就可能变为过期的。对于每个chunk,master维护一个版本号来区分最新和过期的副本。

 只要master为一个chunk授权一个新的租约,那么它的版本号就会增加,然后通知副本进行更新。在一致的状态下,Master和所有副本都会记录这个新的版本号。这发生在任何client被通知以前,因此也就是client开始向chunk中写数据之前。如果另一个副本当前不可用,它的chunk版本号就不会被更新。当chunkserver重启或者报告它的chunk和对应的版本号的时候,master会检测该chunkserver是否包含过期副本。如果master发现有些版本号大于它的记录,master就认为它在授权租约时失败了,所以采用更高的版本号的那个进行更新。

 Master通过周期性的垃圾回收删除过期副本。在此之前,对于客户端对于该chunk的请求master会直接将过期副本当作根本不存在进行处理。作为另外一种保护措施,当master通知客户端那个chunkserver包含某chunk的租约或者当它在clone操作中让chunkserver从另一个chunkserver中读取chunk时,会将chunk的版本号包含在内。当clinet和chunkserver执行操作时,总是会验证版本号,这样就使得它们总是访问最新的数据。

 5.容错和诊断

在设计系统时,一个最大的挑战就是频繁的组件失败。组件的数量和质量使得这些问题变成一种常态而不再是异常。我们不能完全信任机器也不能完全信任磁盘。组件失败会导致系统不可用,甚至是损坏数据。我们讨论下如何面对这些挑战,以及当它们不可避免的发生时,在系统中建立起哪些工具来诊断问题。

 5.1高可用性

在GFS的数百台服务器中,在任何时间总是有一些是不可用的。我们通过两个简单有效的策略来保持整个系统的高可用性:快速恢复和备份。 

5.1.1快速恢复

Master和chunkserver都设计得无论怎么样地被终止,都可以在在几秒内恢复它们的状态并启动。事实上,我们并没有区分正常和异常的终止。服务器通常都是通过杀死进程来关闭。客户端和其他服务器的请求超时后会经历一个小的停顿,然后重连那个重启后的服务器,进行重试。6.2.2报告了观测到的启动时间。

 5.1.2chunk备份

正如之前讨论的,每个chunk备份在不同机柜上的多个chunkserver上。用户可以在不同名字空间内设置不同的备份级别,默认是3.当chunkserver离线或者通过检验和检测到某个chunk损坏后(5.2节),master会克隆现有的副本使得副本的数保持充足。尽管副本已经很好的满足了我们的需求,我们还探寻一些其他的具有同等或者更少code的跨机器的冗余方案,来满足我们日益增长的只读存储需求。我们期望在我们的非常松散耦合的系统中实现这些更复杂的冗余模式是具有挑战性但是可管理的。因为我们的负载主要是append和读操作而不是小的随机写操作。

 5.1.3master备份

为了可靠性,master的状态需要进行备份。它的操作日志和检查点备份在多台机器上。对于状态的变更只有当它的操作日志被写入到本地磁盘和所有的远程备份后,才认为它完成。为了简单起见,master除了负责进行各种后台活动比如:垃圾回收外,还要负责处理所有的变更。当它失败后,几乎可以立即重启。如果它所在的机器或者硬盘坏了,独立于GFS的监控设施会利用备份的操作日志在别处重启一个新的master进程。Client仅仅使用master的一个典型名称(比如gfs-test)来访问它,这是一个DNS名称,如果master被重新部署到一个新的机器上,可以改变它。

 此外,当主master down掉之后,还有多个影子master可以提供对文件系统的只读访问。它们是影子,而不是镜像,这意味着它们可能比主master要滞后一些,通常可能是几秒。对于那些很少发生变更的文件或者不在意轻微过时的应用程序来说,它们增强了读操作的可用性。实际上,因为文件内容是从chunkserver中读取的,应用程序并不会看到过期的文件内容。文件元数据可能在短期内是过期的,比如目录内容或者访问控制信息。

 为了保持自己的实时性,影子服务器会读取不断增长的操作日志的副本,然后像主master那样将这些变化序列应用在自己的数据结构上。与主master一样,它也会在启动时向chunkserver拉数据来定位chunk的副本,也会同它们交换握手信息以监控它们的状态。只有在主master决定创建或者删除副本时引起副本位置信息更新时,它才依赖于主master。

 5.2数据完整性

每个chunkserver通过校验和来检测存储数据中的损坏。GFS集群通常具有分布在几百台机器上的数千块硬盘,这样它就会经常出现导致数据损坏或丢失的硬盘失败。我们可以从chunk的其他副本中恢复被损坏的数据,但是如果通过在chunkserver间比较数据来检测数据损坏是不现实的。另外,有分歧的备份仍然可能是合法的:根据GFS的变更语义,尤其是前面提到的原子性的record append操作,并不保证所有副本是完全一致的。因此每个chunkserver必须通过维护一个检验和来独立的验证它自己的拷贝的完整性。

 一个chunk被划分为64kb大小的块。每个块有一个相应的32bit的校验和。与其他的元数据一样,校验和与用户数据分离的,它被存放在内存中,同时通过日志进行持久化存储。

 对于读操作,chunkserver在向请求者(可能是一个client或者其他的chunkserver)返回数据前,需要检验与读取边界重叠的那些数据库的校验和。因此chunkserver不会将损坏数据传播到其他机器上去。如果一个块的校验和与记录中的不一致,chunkserver会向请求者返回一个错误,同时向master报告这个不匹配。之后,请求者会向其他副本读取数据,而master则会用其他副本来clone这个chunk。当这个合法的新副本创建成功后,master向报告不匹配的那个chunkserver发送指令删除它的副本。

 校验和对于读性能的影响很小,因为:我们大部分的读操作至少跨越多个块,我们只需要读取相对少的额外数据来进行验证。GFS client代码通过尽量在校验边界上对齐读操作大大降低了开销。另外在chunkserver上校验和的查找和比较不需要任何的IO操作,校验和的计算也可以与IO操作重叠进行。

 校验和计算对于append文件末尾的写操作进行了特别的优化。因为它们在工作负载中占据了统治地位。我们仅仅增量性的更新最后一个校验块的校验值,同时为那些append尾部的全新的校验块计算它的校验值。即使最后一个部分的校验块已经损坏,而我们现在无法检测出它,那么新计算出来的校验和将不会与存储数据匹配,那么当这个块下次被读取时,就可以检测到这个损坏。(也就是说这里并没有验证最后一个块的校验值,而只是更新它的值,也就是说这里省去了验证的过程,举个例子假设最后一个校验块出现了错误,由于我们的校验值计算时是增量性的,也就是说下次计算不会重新计算已存在的这部分数据的校验和,这样该损坏就继续保留在校验和里,关键是因为这里采用了增量型的校验和计算方式)

 与之相对的,如果一个写操作者覆盖了一个现有chunk的边界,我们必须首先读取和验证操作边界上的第一个和最后一个块,然后执行写操作,最后计算和记录新的校验和。如果在覆盖它们之前不验证第一个和最后一个块,新的校验和就可能隐藏掉那些未被覆盖的区域的数据损坏。(因为这里没有采用增量计算方式,因为它是覆盖不是append所以现有的检验和就是整个块的没法从中取出部分数据的校验和,必须重新计算)。

 在空闲期间,chunkserver可以扫描验证处在非活动状态的trunk的内容。这允许我们检测到那些很少被读取的数据的损失。一旦损坏被发现,master就可以创建一个新的未损坏副本并且删除损坏的副本。这就避免了一个不活跃的坏块骗过master,让之以为块有足够的好的副本。

 5.3诊断工具

全面而详细的诊断性的日志以很小的成本,带来了在问题分解,调试,性能分析上不可估量的帮助。没有日志,就很难理解那些机器间偶然出现的不可重复的交互。GFS生成一个诊断日志用来记录很多重要事件(比如chunkserver的启动停止)以及所有RPC请求和应答。这些诊断日志可以自由的删除而不影响系统的正常运行。然而,只要磁盘空间允许,我们会尽量保存这些日志。

 RPC日志包含了所有的请求和响应信息,除了读写的文件数据。通过匹配请求和响应,整理不同机器上的RPC日志,我们可以重新构建出整个交互历史来诊断一个问题。这些日志也可以用来进行负载测试和性能分析。

 因为日志是顺序异步写的,因此写日志对于性能的影响是很小的,得到的好处却是大大的。最近的事件也会保存在内存中,可以用于持续的在线监控。