该paper是2003年Google发表在SOSP(Symposium on Operating Systems Principles-计算机系统畛域的顶会)的论文《The Google File System》集体翻译。作为Hadoop时代的三架马车之一,对以后分布式系统诸多问题的解决方案具备很重要的意义。
集体翻译不易,转载请备注!
谷歌文件系统
Sanjay Ghemawat, Howard Gobioff, and Shun-Tak Leung (谷歌)~~~~
摘要
咱们曾经设计并实现了google文件系统,它是一个实用于有着大量数据的数据密集型利用的文件系统。它提供了在低成本硬件设施上的运行的容错性能,以及能够在大量客户端背后呈现出高的整体性能。
只管它与之前的一些分布式文件系统有着许多独特指标,然而通过对咱们程序负载状况以及以后和将来的技术环境的考查,发现咱们想要的设计和一些晚期的文件系统所构想是不一样的,这让咱们不得从新扫视传统的设计抉择,并摸索出一些从根本上不同的设计观点。
目前这个文件系统胜利的满足了咱们的存储需要,他曾经宽泛的被利用部署于咱们的一些存储平台(那些须要大数据集的钻研和开发)服务上用于数据的生产和解决。迄今为止咱们最大的集群曾经在超过一千台的机器及数千块硬盘上提供上百TB的数据存储服务,同时能够被数百个客户端并发去拜访。
在本论文中,咱们将提供为了反对分布式应用程序的文件系统扩大接口设计,并对咱们设计的许多方面展开讨论,展现微基准(微基准是一个旨在掂量十分小以及特定代码性能的基准,基准测试是实现对一类测试对象的某项性能指标进行定量的和可比照的测试)和事实中应用的一些测量数据报告。
1.简介
咱们曾经设计和实现了GFS(谷歌文件系统)去满足谷歌飞速增长的数据处理需要,GFS和之前的许多分布式系统有许多独特的指标,比方:性能,可扩大,可靠性,可用性。然而,驱动GFS的设计的还有对咱们程序的工作负载和技术环境的察看,所以说当初包含可预感的未来,GFS都和晚期分布式系统所设计的构想有显著的不同。咱们从新扫视了传统的一些设计抉择,并摸索了一些从根本上不同的设计点。
第一,组件的生效应该是常态的而不是意外事件。文件系统是建设在由成千盈百的便宜机器形成的存储介质,并被成千盈百的机器拜访着。这些机器的组件无论数量还是品质都很难保障在任意工夫都能提供性能,以及一些组件能够主动从失败状态恢复过来。咱们曾经见过很多问题:比方程序的bug,人为的谬误以及磁盘的谬误,操作系统的bug,内存,连接器,网络,以及电力供应。因而,继续的监控,谬误检测,容错,以及主动复原这些个性必须集成在整个文件系统外面。
第二,以传统的规范来看,咱们的文件非常的大,数GB的文件是粗茶淡饭。每个文件通常蕴含许多的程序对象如web文档,当咱们定期操作由数十亿数据对象快速增长形成的数TB的数据集时候,即便文件系统反对,操作数十亿的kb大小的文件也是很慢的很轻便的。所以,设计的假如条件和某些模块的参数,比方IO的操作,数据块大小这些设计元素都须要被重新考虑。
第三,大部分文件变更都是通过追加文件内容而不是从新写入的。对一个文件随机写这种事件简直不会产生!一旦写入,这个文件就是只读的,并且通常是程序读(大量的数据其实都合乎这种个性)。 这些数据文件可能是大量的数据分析和程序扫描的仓库组成,可能是运行中的应用程序一直产生的数据流,可能是一些打包归档的文件,也可能是一些由一个机器生成的后果(该后果须要立即实时或者稍有提早的被另一个机器解决)。如果针对这些大数据都具备这样的拜访模式(append模式),那么当对客户端申请的数据块缓存生效了,数据的append操作就是性能优化和操作原子性的要害。
第四,应用程序和文件系统API的协同设计,进步了整个零碎的灵活性。比方咱们通宽松GFS的一致性模型从而极大的简化的文件系统并且没有给应用程序带来沉重的累赘。咱们也引进了一个原子性的append操作,这样多个客户端就能够并发的对文件append数据而不须要额定的同步。这些都会在论文的前面做具体的探讨。
大量的GFS集群目前曾经部署并服务于不同的目标工作。其中最大的一个集群有超过1000个存储节点,超过300T的磁盘。并且被数百个客户端在不同的机器上大量的继续一直的拜访着。
2.设计概述
2.1设计概述
为了设计一个满足咱们需要的文件系统,咱们须要以后面提到的那些具备挑战和时机的假如为指南。下面咱们曾经提到了一些要害的关注点,当初咱们将更加具体的形容咱们的假如。
- 首先,这个文件系统应该是建设在大量便宜的机器组件上的,当然这些组件也常常会失败。所以就必须不停的自我监控从而能在日常根本工作中能够疾速的对组件失败进行检测->容错->复原。
- 该文件系统只存储适量的大文件,咱们心愿有数百万个文件,每个根本在100M或者更大点。对于上GB的文件也会是一个广泛的状况应该能够被高效的治理,小文件当然也必须反对,只不过咱们不须要去优化他们。
- 文件系统的次要负载次要蕴含这两种读申请:大的程序流式读取和小的随机读取。在大的流式读取中,单个操作个别读取几百KB或者上M的数据。来自同一个客户端的间断申请通常是通过一直读取一个文件的邻近区域。而一个小的随机读取个别是读取某个任意偏移地位上几KB数据。一个具备性能意识的程序会将这些小的读取批化,并且从新编排这些读取操作使得读取能够高效稳固的程序穿过文件,而不是来回跳跃的读取。
- 文件的零碎的负载还有一部分来自于很多大量的、程序的对文件进行追加写。通常这些写操作的数据量大小和读相似。一旦写入,文件就很少被批改。咱们须要反对随机写申请然而不须要保障其效率性能。
- 零碎必须对多个客户端同时并发的追加同一个文件提供良好的实现。咱们的文件常常被用于如生产者-消费者队列或者做多路归并。数百个生产者,一人一个机器,将会并发的对一个文件进行追加。所以最小化的同步机制的原子性是很有必要的。文件应该稍后能够被读取,或者消费者同时的进行读取到文件。
- 高长久的带宽比低提早更加重要,咱们大多数程序的目标是可能高速率的批量解决数据,很少对单个读写操作的响应工夫有严格的要求。
2.2接口
GFS提供了给人很相熟一个的文件系统接口,只管他并没有实现一个像POSIX那样的统一标准的API 【Portable Operating System Interface of UNIX(可移植操作系统接口),POSIX规范定义了不同操作系统对应下层应用程序统一标准的接口】。GFS中文件是依照目录分层级组织的,并通过文件路径名来标识。咱们反对文件的罕用操作如:创立,删除,关上,敞开,读取,写入操作。
此外,GFS有快照和操作记录追加机制。快照是一个以最低开销创立一个文件和目录树的拷贝。记录追加容许多个客户端同时向同一个文件并发的追加数据,并能保障每个客户端追加操作的原子性。对于实现一个多路合并后果及生产者消费者队列这很有用,客户端能够并发的增加数据而不须要做额定的加锁操作。咱们发现GFS所设计的这种文件类型,对于构建大型的分布式应用程序有无奈预计的价值。快照 和 记录追加 将会在3.4和3.3节别离作具体探讨。
2.3架构
一个GFS集群蕴含一个master,多个chunkservers服务,能够被多个客户端拜访。如图一所示:
这下面每一个服务通常都运行在一个独自的Linux机器的用户级过程中。当然咱们也能够运行客户端和chunkserver在一个机器下面,只有机器的资源容许以及能够承受运行可能不稳固的利用程序代码所带来的低可靠性。(意思就是部署的程序可能可靠性差,把机子搞崩了就连带chunkserver也炸了)。
文件被划分成固定大小的chunk。每个chunk是由master在该chunk被创立时调配的一个不可变的、全局惟一的64bit句柄(chunk handle)来标识。Chunkserver将chunk作为linux文件存储在本地,对于chunk数据的读写通过chunk handle和指定字节范畴来确定。为了可靠性,每个chunk将会被备份到多个chunkserver上,默认的咱们存储三个备份。当然用户也能够对文件不同的命名空间区域自定义不同的备份级别。
Master保护所有文件系统的元数据。包含:名字空间,访问控制信息,文件与chunks的映射信息,chunks的以后地位。它也控制系统范畴内的一些流动,比方chunk租赁治理,孤立无援用的chunk的垃圾回收,chunkserver间chunk的迁徙。Master与chunkserver通过周期性的心跳进行通信,从而发送指令和获取chunkserver的状态。
实现了文件系统API的应用程序通过GFS客户端和GFS的master和chunkserver进行连贯进行通信进行数据读写操作。客户端如果须要操作元数据则须要与master通信,其余的所有的纯数据的通信间接与chunksever通信。咱们没有提供POSIX API,因而也就不须要与linux vnode layer关联【unix文件节点】。
客户端和chunkserver都不会进行文件数据缓存。客户端做缓存只能提供很小的益处,因为大部分的利用须要流式读取整个文件或者工作的数据集太大了基本没法缓存。没有这些缓存简化了客户端和整个零碎,打消缓存一致性的问题(但实际上客户端会缓存元数据)。Chunkserver也不须要缓存文件数据,是因为chunk是作为本地存储文件,Linux自带机制曾经帮咱们把那些常常拜访的数据缓存在内存中了。
2.4 单Master
一个独自的master极大的简化了咱们的设计,使得master能够通过它对集群全局的了解来正当的安顿chunks和正本地位的抉择。然而咱们必须最小化master在读写操作中的相关性,使得master不会成为一个性能的瓶颈。 客户端不会从master节点来读取和写入文件而是向master询问它应该分割哪一个chunkservers。并且这个交互的关联信息会被在一个限定的工夫内缓存以用于随后的许多操作能够间接应用缓存的关联信息。
依据图1咱们来形容一次简略的读流程是怎么进行交互的:
- 首先,通过固定大小的chunk size,客户端从程序中指定的文件名和偏移的字节量解析出文件的chunk index。
- 而后,他会向master发送一个蕴含文件名和chunk index的申请。master会返回申请对应的chunk句柄(前面都翻译为chunk handle)和chunk正本的地位信息。客户端会缓存这些信息,以文件名和chunk index为key。
- 随后,客户端会向其中一个正本节点发送申请(通常是离客户端最近的那个)。该申请指定了chunk handle和一个该chunk的字节范畴。前面的所有对该chunk的申请就不在须要和master再进行交互了直到缓存过期或者该chunk的文件被从新关上过。
实际上,客户端通常一次性申请多个chunks的信息,master也能将这些chunks信息一并返回回来。这些额定的信息简直没有任何额定耗费,但能够很好的罢黜未来master和client许多次通信。
2.5 Chunk大小
Chunk 大小是设计参数中一个关键点。咱们抉择了64M为默认大小,它远远大于通常的文件系统块。每一个chunk的正本是一个简略的Linux文件被存在chunkserver上,并只有在须要的时候才扩大。这种惰性空间调配(lazy space allocation)防止了因为内部空间碎片造成的空间的节约,碎片最大可能有一个chunk size那么大。
大的chunk size具备几个很重要的长处:
第一点,他缩小了客户端和master通信的次数。因为在一个chunk上的读和写只须要最后的一次和master申请的chunk地位信息【chunk越大,那么蕴含的内容也就越多,须要的chunk location信息也就越少】,这种交互的缩小对咱们零碎负载来说是非常的重要的。因为咱们的下层程序个别操作都是程序读取大文件。即便是对那些大量的随机读,客户端也能够很轻松的缓存一个几TB的工作数据集的所有的chunk地位信息。
第二点,因为chunk很大,一个客户端就很大可能只会在一个chunk上执行全副的操作了,这样能够通过放弃一个长久的TCP长连贯从而缩小网络的开销。
第三点,他缩小了在master中存储的元数据的体积,这样能够运行咱们放弃所有的元数据在内存中,这所带来的益处咱们会在2.6.1节作探讨。
另一方面,一个大的chunk size,即使采纳了lazy space allocation也有着他的毛病。比方对于一个小文件只有几个chunk,或者只有一个chunk的,如果有大量的客户端须要拜访这个文件那么这个chunk所在的servers就会成为热点。但理论实操中,热点并不是咱们一个次要的问题毕竟咱们的程序大部分都是程序读大的多chunk的文件。
然而,热点问题在GFS在第一次被一个批处理队列零碎应用的时候的确裸露进去了:一个可执行文件被写入GFS的一个单chunk文件中,随后同时启动了上百台机器,那个存储了这个执行文件的chunkserver很快就被数百个同时申请给整超载了。咱们过后通过给该执行文件进步更高的正本数量并将那批处理队列零碎的启动工夫进行错开解决了这个问题。然而久远的解决办法应该是在这种状况下,容许客户端从其余的客户端读取数据【而不是一窝蜂的压力都给在chunkserver上】。
2.6 元数据
master节点次要存储三种类型的元数据:文件和chunk的命名空间(namespace),文件和chunk的映射关系(mapping),每个chunk正本的地位(location)。所有的元数据都存在master的内存中的。前两中(namespace和mapping)还被被长久化到一个记录了变更操作的操作日志中并存储在master的本地磁盘上同时备份到近程机子下来。应用这样的一个日志容许咱们更新master的状态简略、可信赖、并且不须要放心master宕机后状态的不统一【得益于log正本】。master节点并不长久化chunk的地位信息而是在启动的时候被动去询问每一个chunk,以及每个chunkserver退出集群的时候获取。
2.6.1 内存数据结构
因为元数据是存在内存中的,所以master的操作是非常快的。因而,master能够很容易并且高效的在先天对他整个状态做周期性的扫描,这个周期性个别是做chunk的垃圾回收,chunkserver失败时候从新备份,以及为均衡负载和磁盘空间在chunkserver之间chunk的迁徙。在4.3,4.4节咱们会进一步探讨这些流动。
这种纯内存形式的设计存在一个潜在的限度就是chunk的数量,因而整个集群的的容量受限于master有多大的内存。但实际上这并不是一个重大的限度,master对每个64M的chunk保留的元数据小于64KB。大部分的的chunk是满的,因为大部分的文件都蕴含多个chunk,所以也就只有最初一个chunk可能是不满的。相似的,每个文件所对应的namespace也通常小于64KB因为他保留时候会采纳前缀压缩算法进行压缩。
如果须要反对更大的文件系统,只须要对master增加额定的内存就行了,这点开销和元数据全副存在到内存所带来的简略,牢靠,性能和灵活性比起来只是一笔很小的代价。
2.6.2 chunk地位
master并不会对一个给定的chunk有哪些chunkserver保留了他的一个正本的信息保留一个长久化的记录。他只会在他启动的时候,简略的从chunkserver拉取过去这些信息。master通过周期性的心跳检测来监控chunkserver从而管制所有chunk的搁置,并且master能够保障本人信息是更新了的。
一开始咱们也尝试了把chunk的地位信息给长久化到master下来,然而咱们发现如果在启动的时候以及前面周期性的去获取这些信息这样更加简略,因为这样防止了当chunkserver在退出和来到集群,改名,失败,重启等状况下master和chunkserver之间的须要进行同步。在一个数百台服务器的集群中,这些事变太常常了产生了。
了解这个设计决定的另一个形式是明确一个chunkserver对他磁盘上的chunks是否有最终的发言权,在master去保护一套这些信息的一致性视图是没有意义的:因为一个chunkserver上的产生的谬误可能会让他下面的chunk不可见(比方一个磁盘可能损坏了或者不可用了),又或者是一个操作可能重新命名了一个chunkserver。【这段比拟难以了解:次要论述了为什么不在master上长久化地位信息?次要因为这些信息不稳固,不像之前提的那两种那么稳固,对每个chunkserver本人的状态依赖太大,一旦chunkserver出了故障,master没法及时或者主动的感知到哪些chunk有问题了,这时候依照长久化记录的那个再去获取chunk就有问题了。所以长久化没有太大意义,不如定时去获取最新的】
2.6.3 操作日志
操作日志蕴含了要害的元数据变更的历史记录,这是GFS的外围。他不止是元数据变动的惟一长久化记录,他也定义了那些并发操作在逻辑工夫上的工夫线。文件,chunks以及他们的版本(见4.5节)这些都在他们被创立时惟一且永恒的被对应的逻辑工夫所标识。
所以操作日志是非常要害的,咱们必须牢靠的保留。不能在元数据变更还没有被长久化之前对客户端可见。否则,咱们将会失落整个文件系统或者客户端最近的操作,即便chunkserver还保留着。所以咱们将其备份到多个近程的机器上并且在对一个客户端作回应前必须保障该操作的日志曾经被刷写到本地和近程磁盘上了。master在刷写前会批处理多个日志记录一起进行刷写,从而缩小刷写和复制对整个零碎吞吐量的影响。
Master通过重放操作日志来复原文件系统。为了最小化启动工夫 ,咱们必须放弃log在很小的规模。master会在日志增长超过肯定的大小的时候对以后状态设置一个检查点(checkpoints),master就能够从本地磁盘上最近的那一次checkpoint进行复原,这样读取的日志记录就在一个被限度的范畴内了。checkpoint是一个压缩的类B-Tree构造,能够被间接映射到内存中并用于namespace的查找,都不须要额定的解析。这个大大的进步了GFS的复原速度和可用性。
因为创立一个checkpoint须要耗费一些工夫,所以master外部状态的构造是按这么样的一个设计来的:能够既无延时承受一直的变动又能够去创立一个新的检查点。master会启动一个新的线程切到一个新的日志文件去创立检查点。这个检查点蕴含了零碎所有的变更,对一个蕴含了百万文件的集群只有几分钟就能实现。在创立完结后,它会被同时写到本地和近程磁盘上。
复原只须要最新的检查点和之后的日志文件。更老的检查点和日志文件能够轻易删除,只管咱们会保留一些来预防一些事变劫难。在创立检查点的时候产生失败并不会影响零碎的准确性,因为复原的代码会查看并跳过不残缺的检查点。
2.7 一致性模型
GFS应用了一个宽松的一致性模型来反对咱们高分布式的应用程序,但实现起来也绝对于简略和高效。当初咱们探讨下GFS的一致性保障,以及这对程序来说意味着什么。咱们也会着重讲GFS是如何保护这些保障的,然而这些细节会留在论文的其余局部。
2.7.1 GFS的保障
文件的namespace的变更(比方文件的创立)是原子性的。他们只被master解决:命名空间锁保障了原子性和准确性(4.1节);master的操作日志定义了全局全副操作的程序(2.6.3节)。
在一个数据变更后,文件区域的状态取决于该变更操作的类型。不论他是胜利还是失败,或者是并发的变更的。表1是对后果的一个概述。如果所有的客户端总是看见同样的数据,不论他们是从哪一个正本读取的数据那么该文件区域便是统一的。一个文件数据变更后是统一的那么所有的客户端应该都能看到该变更的写入,这时候该文件区域就已定义的。当一个变更胜利并没有被其余的并发写操作影响,那么这个被影响的区域就是已定义的(也就是统一的):所有的客户端会看到变更的写入。并发的胜利更改会让区域处于未定义然而是统一的,所有的客户端看见的是同样的数据,但这不意味着任何一个变更曾经写入了(如果变更是针对雷同的数据写这样有的变更就会被新的变更所笼罩,这样用户就无奈看到最先的变更了,同时产生在跨chunk的操作会被拆分成两个操作,这样这个操作的一部分可能会被其余操作笼罩,而另一部分则保留下来,如3.1节开端所述)。通常的,他看到的是多个变更组合后的后果。一个失败的变更会使区域在一个非统一状态(因而也是未定义):不同的客户端可能会看见不同的数据。咱们上面会形容咱们的程序如何已定义的区域和未定义的区域。应用程序不须要进一步去辨别未定义区域的不同品种。
数据的变更可能是写入或者记录的追加。一个写操作的数据会写入到程序指定的偏移地位上。一个记录的追加会将数据原子的追加,即便是并发的变更也会至多被追加一次,然而偏移地位由GFS来抉择(见3.3节)。(而客户端会认为一个通常的追加是在以后文件的尾部写入一个偏移量地位的数据)偏移会返回给客户端,来标记蕴含了那条记录的已定义区域的开始地位。另外,GFS可能会在他们之间插入一些padding或者记录的正本。他们会占据被认为是不统一的区域,并且通常比用户数据小的多。
在一系列胜利变更后,变更的文件区域能够被保障是已定义的并蕴含了最初一次变更记录的写入。GFS通过两步来实现:a.将这些变更以雷同的程序利用到该chunk的所有正本上(见3.2节),b. 应用chunk的版本号来检测所有曾经变旧的正本(可能是因为chunkserver挂了的时候导致chunk失落了变更信息,见4.5节)。旧的正本永远都不会参加任何变更以及客户端向master询问chunk地位的信息的返回。他们会被优先进行垃圾回收掉。
因为客户端缓存了chunk的地位信息,他们可能会在本地信息更新前读到旧的正本下来。这个本地缓存信息(原文window)由缓存的超时工夫和文件下一次关上的工夫所限度,文件关上会革除该文件在缓存中所有的chunk信息。此外,咱们的大部分操作都是only-append操作,一个旧的正本通常会返回一个chunk晚期append完结地位的数据而不是一个曾经产生更新过期的数据。当读方重试和master分割的时候,能够了解失去以后chunk的地位信息。
在一个变更胜利很久之后,组件失败依然可能净化和损坏数据。GFS通过周期性的在master和所有的chunkserver间握手来找到那些失败的chunkserver,并且通过校验和(checksum)来检测数据的净化(见5.2节)。一旦问题裸露,数据会尽可能快的从正确的正本中复原(4.3节)。只有当一个chunk在GFS对净化做出反馈之前该chunk的所有正本数据曾经全副失落,这个chunk的失落才是不可逆的,而通常GFS的反应时间在分钟内的。即使是在这种状况下:chunk曾经不可用,然而没有被净化:应用程序还是能够收到分明的错误信息而不是净化了的数据。
2.7.2 应用程序的影响
GFS应用程序能够通过应用一些简略的技术来适应这种宽松的一致性模型,这些技术点都是为了某些目标:依赖append而不是笼罩写,checkpoints,自我写校验,自我记录标识。
事实上,所有咱们的程序修改文件都是通过append而不是overwrite。在一个典型的应用场景:一个writer会从头到尾生成一个文件。当它写完所有的数据的时候会对该文件原子性的重命名为一个永恒的名字,或者周期性的通过检查点来查看有多少数据曾经被胜利写入。checkpoints可能会也蕴含利用级的校验和。reader只验证和解决最初一个checkpoints之前的文件区域,因为这些区域是处于已定义的状态了。只管存在一致性和并发性问题,但这种办法对咱们很有用。与随机写入相比,append要高效得多,对应用程序故障的响应能力也更强。checkpoints容许writer增量性地重新启动写(不须要每次都从头开始),并避免reader解决尽管胜利写入了然而从应用程序的角度来看依然不残缺的文件数据。
在另一种典型用法中,许多writer并发的append到一个文件以合并后果或作为生产者 - 消费者队列。 记录append操作的append-at-least-once语义保障了每个writer的输入。reader解决偶然的填充和反复,如下所示。 writer为每条记录筹备了比方校验和等额定的信息,以便验证其准确性。 reader能够应用校验和来辨认和抛弃额定的填充和记录片段。 如果它不能容忍偶尔的反复数据(例如,如它们可能触发非幂等操作),能够应用记录中的惟一标识符来过滤它们,这通常须要命名相应的应用程序实体(例如一个web文档)。 这些记录的I/O性能函数(除了反复删除)都在咱们的应用程序共享的库代码中,并实用于Google的其余文件接口的实现。 有了这个,雷同的记录序列,加上常见的重复记录,总是会被传递给reader。
在以上的形容中,存在一个根本的假设:数据是以record模式存储的,而且通常这些record都是能够反复的,比方一个网页文档咱们能够反复存,这对于数百亿的网页文档来说,存储多数多余的很失常,也就是说这些数据通常是文本,而不是二进制,所以咱们才能够在append或者写时用记录的副原本笼罩非统一的区域,所以提供了append的append-at-least-once语义,因为append二次也是能够的。如果咱们要保障唯一性,能够在应用层减少逻辑。【这一段根本没看懂】
3. 零碎交互
咱们设计这个零碎的目标是为了最小化master在所有操作中的相关度。在这个前提下,咱们当初来探讨下client,master以及chunkservers 是如何交互来实现数据的变更,record的原子append,以及快照的。
3.1 租约(Lease)和变更程序
一个变更就是一个批改chunk的内容或者让元数据的操作,比方一个写或者append操作。每个变更都须要在所有的正本上执行。咱们应用租约来维持多个正本间变更程序的一致性。Master受权给其中一个chunk租约,咱们叫做主正本(primary)。主正本会对该chunk的所有变更抉择一个程序,而后所有的正本就会依照这个程序去利用这些变更。因而,全局的变更程序首先由master来抉择租约受权的程序来确定的(如果多个chunk批改,master程序对给他们进行租约受权,受权其实就是选一个primary来进一步解决),而后在一个租约外面的是交给primary副原本定义操作程序的。
这个受权机制是为了最小化master的治理开销所设计的。一个租约有60s的超时工夫设置。然而只有这个chunk正在产生变更,主正本就能够和master申请缩短租约。这些扩大的申请和受权通常是通过master和chunkserver的心跳信息一起发送的。master有时候可能想在租约到期前撤销租约(比方master想要进行对一个被重命名的文件进行变更操作)。即便master和主正本失去通信,该机制能够让master平安的在旧的租约过期后,受权一个新的租约给新的正本。
在图2中,咱们依照上面的程序步骤来论述一个写操作的过程。
1. 客户端会申请master哪一个chunkserver持有以后的租约,以及其余的正本地位。如果没有正本持有租约,master会选一个正本受权租约给它(这里没有展现)。
2. master返回该主正本的标识一起其余正本的地位信息。客户端缓存这些数据用于将来的变更。只有当主正本没有了响应或者发现租约曾经到期了,client才会和master分割。
3. 客户端向所有的正本推送数据。客户端能够依照任意的程序推送数据,么个chunkserver会将数据缓存在外部的LRU的buffer外面直到数据须要被应用或者过期。 通过控制流和数据流的解耦,咱们能够将重要的数据流基于网络拓扑来进行调度,而不是去思考哪一个是主正本,从而进步性能。这在3.2节咱们会深刻探讨。
4. 一旦正本确认收到了数据,客户端会发一个写申请给主正本。该申请标识了之前推送给所有正本的数据。而后主正本会调配一个间断的序列号给所有她承受到的变更(可能来自于多个client)。而后主正本会将依照后面的序列好将变更利用爱本地的正本上。
5. 主正本将写申请发送给所有的其余正本,每个正本都按主正本调配的序列号利用这些变更。
6. 等其余正本都写入实现了,会返回给主正本操作实现。
7. 主正本会响应给客户端。任何正本碰到的错误信息都会返回给client。呈现谬误时,写操作可能曾经在主正本以及局部正本上执行胜利了(如果是主正本失败了,就不会有变更的序列号调配给其余的正本也就是不会有前面的操作了)。如果遇到谬误,那么客户端的申请会被认为是失败的,批改的region认为是处于不统一的状态。咱们客户端的代码代码会通过重试这些变更来解决这样的谬误。它会先在3-7步骤进行一些尝试后再从头重试写操作。
如果app的一个写操作是很大的操作或者跨chunk边界的,GFS客户端代码会将他们分为多个写操作。这些操作都遵循下面的控制流,然而可能会被其余client的并发操作插入或者笼罩。因而共享的文件区域最终可能会蕴含了来自不同clinet的片段,尽管这些正本之间是统一的(因为他们所有的操作都是依照雷同的程序执行的),然而文件区域会处于一种统一然而未定义的状态,如2.7提到的那样。【这个黄东旭如同提到过】
3.2数据流
为了高效的应用网络,咱们将数据流和控制流拆散。当控制流从client到主正本再到其余的所有次正本节点时,数据流则是通过一个认真抉择进去的chunkserver链,将数据线性的流水线的形式进行推送。咱们的目标是充分利用每个机器的带宽,防止网络瓶颈和高提早的连贯,最小化推送数据的提早。
为了充分利用每个机器的网络带宽,数据是在一个chunkserver链线性的推送的,而不是以其余的拓扑构造进行散布的(比方:树结构)。因而,每个机器的带宽能够全副用来发送数据,而不是被多个接收者所切分。
为了防止网络瓶颈和搞提早的连贯(比方:交换机的网络链路就常常会这样),每个机器都会将数据传输到在网络拓扑中距离最近的,并且没有收到数据的那个机器推送数据。咱们假如客户端要推送数据到chunkserver S1到S4。他先推送给最近的机器S1,而后S1推送给最近的S2,相似的S2给S3或者S4(距它最近的那个就行),以此类推。咱们的网络拓扑很简略,所以间隔能够通过IP地址准确的被估算进去。
最初,为了最小化提早,咱们将数据通过TCP进行连贯。一旦一个chunckserver收到一些数据,它就会立即开始推动这些数据。流水线对咱们来说非常有用,因为咱们应用了一个全双工链路的替换网络,这样在承受的时候立即进行发送数据不会影响到咱们承受数据的速度。如果没有网络拥塞,理想化的向R个正本传输B字节的数据须要的工夫是B/T+RL(T代表网络吞吐率,L是机器间的网络提早)。咱们网络的链路通常是100Mbps(T)的吞吐,L也远远低于1ms,因而1M的数据在现实状况下80ms就能够散发实现。
3.3原子性的记录append
GFS提供一个原子性的append操作叫做record append(留神这与传统的append操作也是不同的)。在一个传统的写操作中,client来指定数据须要写的偏移地位。对于雷同区域的并行写操作是无奈串行的:该区域最终可能蕴含来自多个client的数据片段。但在一个record append操作中,client只须要指定数据。GFS会将它至多一次的原子性地append到文件中(比方:像一个继续一直的字节流一样),数据偏移的地位由GFS确定,同时将该偏移地位返回给client。这很相似于unix文件关上模式中的O_APPEND模式,当多个writer并发写操作时不会产生竞争条件。
Record append在咱们分布式应用程序中被重度应用,很多在不同机器的client会并发地向同一个文件进行append。如果应用传统的写操作,client就会须要进行简单而又低廉的同步化操作,比方通过一个分布式锁管理器。在咱们的工作中,这样的文件通常是被用来服务于一个多生产者/单消费者队列或者保留来自多个不同client的归并后果。
Record append是一种变更操作,除了一点在主正本上的额定的逻辑外仍然遵循3.1节的控制流逻辑。Client先将所有的数据推送给所有的正本后,再向主正本发送申请,主正本会查看将记录append到该chunk是否会导致该chunk超过它的最大值(64MB)。如果超过了,它就将该chunk填充到最大值,并通知次正本做同样的工作,而后告诉Client上面的操作应该在下一个trunk上重试了。(append的record大小须要管制在trunk最大值的四分之一以内,以保障最坏状况下的碎片在一个能够被承受的等级)。如果append后chunk没有超过最大值,那就是通常的状况,主正本会将数据append到它的正本上,而后通知次正本将数据写在雷同的偏移地位上,最初向client返回胜利响应。
如果record append在任何一个正本上操作失败,client会重试这个操作。那么雷同chunk的多个正本就有可能蕴含不同的数据,这些数据可能蕴含了雷同记录的整个或者局部的反复值。GFS并不保障所有的正本在比特级别上的一致性,它只保证数据作为一个原子单元起码被写入了一次。这个属性能够很容易地从一个简略的察看中得出:为了让操作返回胜利,数据必定要被写入到某个trunk的所有正本的并在雷同偏移地位上了。此外,所有的正本都应该达到了记录尾部,因而将来的记录将会被搁置在更高的偏移地位,或者是另一个不同的chunk,即便当前另一个正本变成了主正本。在咱们的一致性保障里,record append操作胜利的数据区域就是已定义的(因而必定是统一的),而介于其间的数据则是不统一的(因而也是未定义的)。咱们的应用程序能够解决这样的不统一区域,如咱们在2.7.2里探讨的那样。【这里看的也是一脸懵逼】
3.4快照
快照操作能够简直实时的制作文件或者目录树的一个拷贝,同时能够最小化对于正在执行的变更操作的中断。咱们的用户用它来创立大数据集的分支拷贝(以及拷贝的拷贝),或者用来创立以后状态的checkpoint,在试验当前能够轻松提交批改或着回滚的更改之前的状态。
像AFS[5],咱们应用规范的写时拷贝技术来实现快照。当master收到一个快照申请时,它首先撤销那些要进行快照的的文件对应的chunks所有已收回的租约。这让这些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,这样client就能够失常的写这个chunk了,而不晓得该chunk实际上是从一个现有的chunk刚刚被创立进去的。
4. master操作
master执行所有的namespace操作,另外他还治理着整个零碎的chunk正本:如决定如何搁置,创立一个新的chunk以及其正本,协调整个零碎的流动来保障chunk都被残缺的备份的,对所以的chunkserver负载平衡,以及回收未应用的存储空间。当初咱们将别离探讨这些主题。
4.1 namespace治理和锁
master的很多操作都非常的耗时间:比方,一个快照操作须要撤销所有的该快照相干的chunkserver的租约。咱们不想在运行这个的时候耽误到别的master操作。因而,咱们通过在namespace的region上应用锁来保障正确的串行化来容许多个操作能够同时运行。
不同于很多传统的文件系统,GFS没有一个用于列出该目录下所有文件的一种目录数据结构。也不反对对同一个文件或者目录起别名(比方unix零碎外面的硬链接和软链接)。GFS将文件的全门路到元数据的映射作为一个查找表逻辑上示意namespace。通过前缀压缩,这个表能够在内存中高效的示意进去。每个在namespace树下的节点(要么是一个文件的绝对路径要么是一个目录的绝对路径)都有一个相关联的读写锁。
每个master的操作在其运行前都须要一系列锁来配合。比方,如果要操作/d1/d2/.../dn/leaf(叶子),那么他就须要从/d1,/d1/d2,... 到/d1/d2/.../dn这些目录的读锁,以及d1到叶子leaf节点的全门路的读锁或者写锁。阐明下leaf可能是个文件或者目录,这取决于是什么操作。
咱们当初来阐明下该锁机制是如何避免在/home/user正在被快照到/save/user时,/home/user/foo被创立。首先快照操作获取/home和/save上的read-lock,/home/user和 /save/user上的写锁。而文件创建操作须要/home和/home/user上的读锁,在/home/user/foo上的写锁。这两个操作将会被精确的串行化执行,因为他们都要获取/home/user上的锁。文件创建操作并不需要父级目录的写锁,是因为咱们没有目录或者相似于inode的数据结构来避免被批改,应用读锁曾经足够无效避免父级目录被删除了。
这种锁模式的一个益处就是它容许对同一个目录进行并发的变更操作。比方多个文件创建操作能够并发的在同一个目录外面执行。每个操作须要一个目录的读锁和一个文件名的写锁。在目录上的读锁能够避免目录被删除,重命名或者快照。针对文件的写锁能够保障雷同文件名的只会被创立一次。
因为namespace有很多节点,读写锁对象是懒加载调配的并且一旦不再应用了就会被删除。并且,这些锁须要一个全局统一的获取程序来防止死锁:这个程序先按namespace树的等级来排,同一个等级依照字典序来排。
4.2 备份搁置
一个GFS集群在很多层面上都是高度分布式的。他个别有数百个chunkserver散布在若干个机架上。这些chunkserver可能被来自于雷同或者不同的机架上的很多个客户端拜访。在不同机架上的两个机器通信可能会跨一个或多个网络替换。另外,进出一个机架的带宽可能会小于机架内所有机器的宽带总和。多级的分布式对数据的散布收回了扩展性,可靠性和可用性的挑战。
chunk正本的搁置策略出于练歌目标:最大化数据的牢靠和可用性,最小化网络带宽的应用。出于这两个目标,不仅仅只是把正本扩散到不同的机器上,这只能应答机器或者磁盘的失败,以及最大化利用每个机器的网络带宽。咱们必须将这些正本扩散在这些机架上(而不是机器上)。这样能够保障当一个机架整个损坏或者离线的时候(比方因为网络交换机故障或者电路问题)chunk的某些正本仍然是存活的可用的。这也意味着对于一个chunk的流量,尤其是读操作能够充分利用多个不同机架的带宽。另一方面,写操作流量得须要通过多个机架间进行,但衡量下咱们是能够承受的。
4.3 创立、重备份、重均衡
创立chunk正本有三个起因:chunk的创立,重备份,重均衡。
当master创立一个chunk时,须要抉择哪里去寄存这些空的正本。会思考上面几个因素:(1)咱们要将新的正本寄存在那些磁盘使用率低于平均水平的磁盘上,这样随着工夫的推移整个chunkservers上的磁盘使用率会大致相同(2)咱们要限度每个chunkserver上的最近创立的次数,只管创立它自身是个便宜操作,然而创立后往往追随的大量的写操作,因为创立就是为了去写正本数据的嘛,在咱们 一次append-屡次读的工作负载下,一旦数据被写入,那么这些数据都是只读的。(3)如下面提到的,咱们要将chunk的正本散布在整个机架群上。
一旦可用的chunk备份数量低于用户指定的数量,master就须要从新备份一个chunk。这个可能有多个起因导致:一个chunkserver变得不可用了,chunkserver报告他的备份曾经毁坏了,磁盘因为某些谬误变得不可用了,或者正本的指标数量被用户进步了。每个须要被重备份的chunk的优先级由上面几个因素决定:一个是它与指标的备份的目标值差多少,比方绝对于失落了1个正本的chunk咱们会给一个失落了2个正本的chunk更高的优先级。另外,咱们会率先重备份那些依然活着的chunk而不是那些最近曾经被删除的文件(见4.4节)。最初,咱们为了最小化对运行中的程序的影响,咱们会晋升任何阻塞了用户过程的chunk的优先级。
master筛选优先级最高的chunk并且通过告诉一些chunkserver去间接从一个无效的正本上复制chunk数据来进行clone。这些新正本的寄存和后面新创建的有类似的指标:均匀磁盘使用率,限度单台chunkserver上的clone操作数量,跨机架正本散布。为了防止clone的流量吞没client的流量,master同时限度集群和每个chunkserver的clone操作数量。另外,每个chunkserver会通过限度源chunkserver的读申请来限度它们在每个clone操作上破费的带宽(我的了解是:clone须要从源chunk复制数据,他管制该读来管制带宽应用)。
最初,master会周期性的对正本进行重均衡:它会查看以后的正本散布,而后挪动正本到更好的磁盘空间来进行负载平衡。这个过程是一步步的填充一个新的chunkserver的,而不是一下子大量的写入这些新的chunk。对于一个新的正本的寄存规范和下面探讨的类似。另外,master必须还要抉择移除哪个曾经存在的正本。总的来说,master更偏向于挪动这些chunk到低于磁盘均匀利用率的chunkserver上来均衡磁盘使用率。
4.4 垃圾回收
在一个文件被删除后,GFS不会立刻开释可用的物理存储,而是会提早解决等代替文件和chunk级别的垃圾回收的时候来操作。咱们发现这种形式能够让零碎变的更加简略和牢靠。
4.4.1 机制
当一个文件被应用程序删除后,master会立即将该删除记录计入日志,就和其余的一些更变操作一样。然而却不会立即开释资源,该删除的文件只是被重命名为一个蕴含了删除工夫戳的暗藏文件名。在master对namesoace进行惯例扫描的时候,如果发现这中暗藏的文件曾经存在了3天以上了就才会被移除(这个工夫距离是能够配置的)。在这个之前,该文件依然是能够通过这个新的被重命名的非凡名字读取的,也能够通过重新命名它为失常名字来撤销删除操作。当那些暗藏文件名的文件被从namespace中删除后,他在内存中的元数据就会被擦除了。这样就无效的切断了所有和他的chunk的关联了。
在master对namespace的一次惯例扫描中,master会标识出那些孤立的chunk(那些无奈从任何文件能够援用达到的)并在元数据中擦除这些chunks。在chunkserver和master的每次心跳中,chunkserver会向master报告一个汇合,该汇合是该chunkserver蕴含了那些孤立chunks的子集(是吗?还是说就是该chunkservers有的chunks的一个任意子集?)。而后master会返回给哪些曾经不存在master元数据中的chunks的标识。而后chunkserver就能够自在的删除这些chunk的正本了。
4.4.2 探讨
只管分布式垃圾回收是一个很难的问题,它对于程序语言上下文须要简单的解决方案。然而在咱们的例子中就非常简略了。咱们能够很容易标识chunk的所有的援用:他们在由master保护的一个文件-chunk的映射表上。咱们也能很容易的标识所有的chunk正本:他们都是一些在chunkserver上指定目录的linux文件。任何对于master来说是未知的chunk的正本就认为是“垃圾”。
应用垃圾回收的形式来回收存储空间比间接删除有几个长处:第一,这对于组件失败很常见的大规模的分布式系统来说非常的简略和可信赖。chunk的创立可能在一些chunkserver上曾经胜利创立了,然而在另外一些失败,这样就有master不晓得的存在的正本了(没懂这什么意思啊,两者有什么关联吗)。正本的删除信息也可能在通信中失落,master须要可能记住信息并从新发送给这些失败的节点。不论是master还是chunkserver的垃圾回收都要提供一种对立的形式去清理那些不晓得有没有用的正本。第二,master会将这些垃圾回收流动和其余的惯例后盾流动合并,比方惯例的namespace扫描和chunkservers的握手。所以这些流动是批处理的并且开销是摊派的。此外,这些流动只会在master绝对闲暇的时候执行,master须要可能迅速的响应客户端的申请。第三,这种垃圾回收提早删除的形式给那些不可逆转的删除操作提供了一个平安的保护网。
依据咱们的教训,这种提早删除的次要害处就是会有时候妨碍用户在磁盘缓和的时候对磁盘应用调整。那些一直创立和删除的临时文件的程序可能不能立即就能从新利用磁盘空间。咱们通过当已删除的文件再次被删除的时候减速他的垃圾回收来解决这个问题。咱们也容许用户在不同的命名空间应用不同的重备份和垃圾回收策略。例如,用户能够指定某些目录树下的所有chunk文件不存储备份,任何删除文件的操作都是即时的并会永恒的从文件系统状态中移除。
4.5 过期正本检测
当一个chunkserver失败了或者停机下,该机的chunk的正本可能就失落了一些变更从而变的过期。对于每个chunk,master保护了一个chunk的版本号来辨别最新的和过期的正本。
一旦master对一个chunk受权一个新的租约,就会减少该chunk的版本号并告诉所有的正本更新。master和这些正本都会在他们长久化的状态中记录这个新的版本号。这产生在任何客户端被告诉前,也就是客户端能够开始往chunk写数据前。如果另一个正本以后是不可用的,他的chunk的版本号就不会被更新。当该chunkserver重新启动并报告他的chunk汇合和他们的版本号信息的时候,master会检测到这个chunkserver有一个过期的正本。如果master发现有些版本号大于他记录的,master就认为他受权租约的时候失败了,就会用这个更布告的版本号来进行更新。
master通过周期性的垃圾回收来移除这些过期的正本,在这之前,客户端申请无关该过期的chunk的信息都会间接被认为不存在来回应。作为另外一种保护措施,当告诉客户端哪一个chunkserver持有租约的时或者批示某个chunkserver执行clone操作,从另一个chunkserver读取chunk数据时候,master会蕴含这些chunk的版本号信息。客户端和chunkserver在执行这些操作的时候会验证这些版本号来保障他们所操作的chunk是最新的数据。
5. 容错和诊断
咱们在设计这个零碎时其中一个最大的挑战就是解决频繁的组件失败。组件的数量和品质使得这些问题成为一个常态而不是异样:咱们不能齐全的信赖机器,也不能齐全置信磁盘。组件的失败会导致系统不可用,更蹩脚的话会损坏数据。咱们当初就来探讨下咱们遇到的这些挑战,以及咱们为零碎构建的一些工具来诊断那些不能防止产生的失败状况。
5.1 高可用
在GFS数百台的服务器中,任意给定工夫总是会有一些机器变得不可用。咱们应用两个简略然而无效的策略来保障整个零碎的高可用:疾速回复和备份。
5.1.1 疾速复原
master和chunkserver两者都设计成无论他们如何被终止都能够在几秒内复原他们的状态并启动。事实上,咱们并不辨别失常和不失常的终止;服务器通常都是通过杀死过程来敞开的。客户端和其余的服务器在其未实现的申请超时的时候会有一个小的进展,而后去从新连贯那个重启后的服务器并重试。6.2.2节会给出观测工夫的报告。
5.1.2 chunk备份
正如之前所探讨的,每个chunk会备份在不同的机架的多个chunkserver上。用户能够对不同的namespace指定不同的备份等级,默认值是3。master会依据须要克隆曾经存在的副原本放弃每个chunk在一些chunkserver掉线或者发现备份数据曾经损坏状况下(通过校验和,见5.2节)这些chunk的齐全备份。只管正本曾经很好的满足了咱们需要,咱们仍然摸索其余模式的一些跨机器的冗余计划,比方应用等同或者更少的代码【原文就没看懂parity or erasure codes】来满足咱们一直增长的只读存储申请。咱们认为在咱们松耦合的零碎中实现这些更简单的冗余模式是很有挑战,然而须要能够被治理的,因为咱们的流量次要是append操作和读操作而不是随机写操作。
5.1.3 master备份
为了牢靠,master的状态也会被备份。master的操作日志和检查点都被备份在多台机器上。一个对master状态的变更只有在日志记录曾经齐全刷写到本地磁盘和其余所有的正本之后才会认为是变更提交了。为了简略起见,master仍然掌控着所有的变更操作和后盾流动比方垃圾回收这些外部批改着零碎的流动。当这些流动或者变更失败了,简直能够立刻重启。如果master所在的机器或者磁盘失败了,在GFS内部的一个监控基础设施就会在别的中央通过备份的操作日志从新开启一个新的master过程。客户端能够应用master的一个权威的名字(其实就是别名,比方 gfs-test)来拜访,这个名字应该一个DNS别名,如果master被重新分配到别的机器上了,咱们能够批改DNS别名的映射来重定向。
此外,甚至在主master曾经挂掉了的状况下,“影子”master仍然能够为这个文件系统提供只读拜访。他们是影子,并不是镜像,所以他们可能会比主master略微落后一些,通常是几秒。对于那些不常常变更文件或者应用程序不介意获取到的是有点旧的后果,这进步了读的可用性。实际上,因为文件内容是从chunkserver上读的,程序并不能看到过期文件的内容。真正可能在短时间内过期的是文件的元数据,如目录内容或者访问控制信息。
影子master为了保障本身的实时性,一个影子master会读取一个一直增长的操作日志的正本,而后将这些操作序列利用在本人的数据结构上,这就和主master做的一样。和主master一样,它在启动后会轮询chunkserver(之后很少轮询)来定位chunk的正本地位,并通过频繁的握手替换信息来监控他们的状态。只有在master决定创立或者删除一个正本导致正本地位更新时候,影子master才会依赖主master。
5.2 数据完整性【整个这章我对校验和这个货色都不是分明,到底是怎么个模式】
每个chunkserver应用校验和来检测存储的数据是否被毁坏了,一个GFS集群常常有数千台磁盘在数百台机器,这就很容易产生磁盘损坏从而在读和写门路上产生数据的损坏或者失落(见第7节的一个起因)。咱们能够通过其余的chunk的副原本复原损坏的数据,然而通过跨chunkserver的正本比拟来检测chunk的损坏是不切实际的。另外,有一致的正本依然可能是非法的:GFS语义的变更特地是后面探讨过的原子性的append记录操作,并不能保障所有的正本是完全一致的。因而,每个chunkserver必须要通过维持一个校验和来独立验证他和本人的拷贝的完整性。
一个chunk被宰割成64KB大小的块,每个块相应的有32bit的校验和。和其余的元数据一样,校验和被和用户数据拆散保留在内存中,并被长久化存储。
对于读操作,chunkserver 在返回任何数据给请求者之前,会校验那些读取边界重叠的数据块的校验和。不论请求者是client还是其余的一个chunkserver。因而chunkserver就不会流传损坏的数据给其余的机器。如果一个块记录的校验和和记录的不统一,chunkserver会返回一个谬误给请求者,并报告这个谬误给master。作为回应,请求者就会从其余的副原本读取,同时master会从其余的正本clone这个chunk。让一个无效的新的正本创立好了,master会批示那个报告失败的chunkserver删除那个失败的正本。
校验和对于读的性能影响很小,有上面几个起因:因为大部分咱们的读操作至多跨了几个块,咱们只须要读取绝对很少的额定数据来验证。GFS客户端通过尝试在块校验和边界上排序这些读操作来大大减少开销(是不是就是让读操作对于块来说更加的程序性,而不是太随机)。此外,在chunkserver上查找和比拟校验和不须要通过任何IO就能够实现,校验和的计算操作也能够和IO操作同时的进行。
对于一个chunk尾部append操作(而不是写笼罩已存在的文件)的校验和计算通过了深度的优化,因为他们在咱们的工作负载中占有统治位置。咱们只有增量的更新最初一部分校验块的校验和,并为那些append在尾部曾经填充好了的新的校验块计算校验值。即便最初一部分的校验块曾经损坏了并且咱们以后没有检测到他,这个新的校验值也不会和存储的数据匹配,当下一次这个块被读的时候就会检测到这个损坏【就是说咱们只是增量计算新块的校验值,然而不会验证其准确性,坏的数据会保留在数据校验和外面,下次读的时候验证就会发现不匹配--这块没咋懂,如同就是这么个意思:咱们只是增量的更新append操作满了而新生成的块的校验和,即便有损坏咱们也不论也不检测,而是在下次读这个块的时候和存储的数据比照(这个数据是不是master的元数据?),一比照就能发现数据损坏了】。
绝对的,如果一个写操作笼罩了一个曾经存在的chunk范畴区域,咱们必须读取和验证这个范畴的第一个块和最初一个块。而后执行写操作,最初再计算和记录新的校验和。如果咱们在覆写这些chunk前不验证第一个和最初一个块,那么新的校验和可能会暗藏那些没有被复写区域的数据的损坏。【因为这里没有采纳增量计算形式,因为它是笼罩不是append所以现有的测验和就是整个块的没法从中取出局部数据的校验和,必须从新计算】
在空间的期间,chunkserver能够扫描并验证那些没有在活动状态的chunk。这让咱们能够检测那些很少被读取的文件的是否有损坏。一旦发现了损坏,master能够创立一个新的没有损坏的正本并删除了曾经损坏的正本。这就防止了一个不沉闷然而损坏了的trunk骗过master,让master认为该chunk有足够的好的正本。
5.3诊断工具
全面而具体的诊断性的日志以最低的老本来帮忙咱们对问题合成,调试和性能剖析。没有日志,咱们就很难分明那些机器间短暂的,不可反复的交互。GFS生成诊断日志记录了很多宝贵的工夫(比方chunkserver的启动和敞开)以及所有的RPC申请和响应。这些诊断日志能够自在的删除而不会影响零碎的准确性。然而咱们在空间容许的状况下应该尽可能的保留这些日志。
RPC的日志蕴含了线路上所有的申请和响应的信息,除了读写文件的数据。通过匹配响应的申请和整顿在不同机器上的RPC记录,咱们能够从新构建出整个交互历史过程来诊断一个问题。这些日志也能够用来跟踪负载测试和性能剖析。
日志带来的性能影响是很小的(和他带来的益处比这些性能的损耗不算什么),因为日志是异步并程序的被写出的。那些最近产生的事件仍然保留在内存中,用于继续的在线监控。
6.测量
在这节,咱们将展现一些小规模基准测试来阐明GFS的架构和实现的固有瓶颈,有些试验数据来自原google理论在应用的集群。
6.1小规模基准测试
咱们在一个蕴含1个master节点,2个master备份,16个chunkserver,16个client组成的GFS集群来测试GFS的性能。须要阐明下,这个配置是为了不便测试,理论状况中集群通常会有数百个chunkserver和数百个client。
所有的机器都装备双核1.4GHz PIII处理器,2G的内存,2个80G5400转的硬盘,和一个100兆的全双工以太网带宽连贯到HP2524交换机上。所有的19台GFS服务器都连贯到了一个交换机上,其余16台客户端连贯到另一个上。这两个交换机之间应用1G的线路连贯。
6.1.1读操作
N个客户端同时从文件系统读。每个客户端随机的在320G的文件汇合中抉择一个4M的region区。每个客户端最初要读到1个G的数据,所以须要反复该步骤256次。chunkservers一共就32G的内存,所以咱们预计至多有10%的命中率到了linux缓存。咱们的后果应该跟靠近一个无缓存的后果。
图3展现了展现了该N个客户端的读取速率的聚合后果和实践上的极限值。125M/s是在1GB的交换机连贯下的峰值,相似的,所以客户端100M带宽的状况下饱和时候每个客户端的流量应该是12.5M/s。观测的后果是如果只有单个client在读时候,读取速度在10M/s,在客户端极限的80%左右。对于16个客户端一起在读的时候,读取速度在94M/s。在125M的峰值下大略利用率在75%左右,也就是单台6M/s左右。因为reader个数的减少效率从80%升高到75%。多个客户端同时拜访同一个chunkserver的概率也同时变大。
6.1.2写操作
N个客户端同时想N个不同的文件写数据。每个客户端以单次1M的一系列写操作向一个新文件写入1GB的数据。这统计的写速度和他的实践极限值如图3-b。这个极限值为67M/s,因为咱们须要把每个字节写入到16个chunkserver中的3个,并且每个只有12.5M/s的输出网络连接。
单个客户端的写速度是6.3M/s差不多是极限值的一半,次要是因为咱们的网络协议栈,他不能很好的利用咱们用于chunkserver正本推送所有设计的流水线模型。在传输数据从一个正本到另一个的时候的提早升高了整体的写性能。
16台客户端整体的写速度达到了35M/s(大略每个2.2M/s),大略也是理论值的一半。和读操作一样,咱们在并发读取的客户端增多的时候就增大了读到同一个chunkserver的可能性,此外,比起16个reader,16个writer的还会增大抵触,因为每个写操作都须要关联三个不同的正本。
写操作比咱们想要的要慢,但在理论中这还没有成为一个次要的问题,因为只管它减少了每个客户端的提早,然而他并不会显著影响系统对大量客户端的写入总带宽。
6.1.3记录追加
图3-c展现了record的append性能。N个客户端同时append到一个文件上。性能取决于保留该文件最初那个chunk的chunkserver的网络带宽,和客户端的的数量无关。该性能从一个客户端的6M/s升高到16个客户端的4.8M/s,大部分是因为网络拥塞和不同客户端的不同的网络传输速率导致的。
咱们程序偏向于冰箱的创立多个这样的文件。或者说,N个客户端并行的向M个分片append,并且这个N和M都是数百个的级别。因而咱们试验中网路的拥塞不是一个次要的问题,因为客户端能够让本人在让本人解决流程,在一个chunkserver比较忙的时候去append另一个。
6.2事实世界的集群
咱们当初查看谷歌外部的两个正在应用中,具备类似集群代表性的集群。集群A被上百个工程师失常来用作钻研和开发应用。一个典型的工作就是被一个真人用户初始化而后运行数个小时。它读取从几M到几个TB的数据,并转换和剖析这些数据,而后将这些后果写回到集群。集群B次要用于生产环境,工作工夫通常更加长,并且是一直的生成和解决数TB的数据集,然而很少有认为的参加。在这两个案例下,一个工作包含了很多在许多机器上并行读和写的很多文件的解决流程。
6.2.1存储
如下面表格展现的5个条目标第一个,两个集群都有数百个chunkserver,领有数TB的磁盘空间并且使用率都是绝对的平等而不是齐全满了的。“已应用空间”蕴含所有的chunk的正本。事实上所有的文件都被备份3次,因而这两个集群别离存储了18T和52T的数据。
这两个集群有很类似数量的文件,只管B集群有大量的死文件,那些曾经被删除或者被新版本文件替换,然而还没有被回收的文件。然而他有更多的chunk,因为他的文件个别都很大。
6.2.2元数据
chunkserver总共存储了数十GB的元数据,大部分都是对那64KB的用户应用块的校验和。chunkserver上惟一的其余的元数据就是4.5节探讨过的那些chunk的版本号。
保留在master中的元数据要更小,只有数十M。或者每个文件均匀大略100字节。这刚好满足咱们理论中master的内存大小不会限度零碎的容量的构想。大部分的文件元数据都是以前缀压缩模式存储的。其余的元数据包含文件的所有者,权限,到chunk的映射,以及chunk的当期版本。另外对于每个chunk咱们存储了以后正本的地位以及用户实现写时拷贝的援用计数。
每个独立的服务器,包含chunkserver和master,只有50M到100M的元数据。因而复原是非常快的,在服务器能够回应申请之前只须要几秒钟工夫从磁盘中读取这些元数据。然而master有时可能会有点慢-通常30到60s,晓得他从所有的chunkserver上拉取到chunk的地位信息。
6.2.3读写速率
表3显示了在各个期间的读写速度。在测量之前,所有集群都曾经启动1周的工夫。(集群最近被重启以降级新版本的GFS)
自从从起后,均匀的写速度小于30M/s。当咱们拿到这些测量值时候,集群B正在处于一个密集的写操作流动中:大略100/s的数据生成速度,因为又要写正本这又造成了300M/s的网络负载。
读的速度远高于写的速度。集群的整体负载和写操作比起来蕴含了更多读操作这和咱们预测的一样。所有的集群都在一个很重的读流动中。尤其是,A曾经在之前的一个星期中始终维持着580MB/s的读速率。它配置的网络能够反对750MB/s,所以它曾经充分利用了资源。B集群可反对1300 MB/s的峰值读速率,然而利用只应用了380 MB/s。
6.2.4 master负载
表3同时也展现了操作被发送到master的速率,大略200-500个/s。master能够很容易的放弃这个速率,所以这些对系统负载来说不是一个瓶颈。
在GFS的早些版本,master偶然对一些工作负载产生瓶颈。master破费大量的工夫在微小的目录(蕴含上千万的文件)中进行线性扫描。因而,咱们改良了master的数据结构使其能够通过namespace来进行二分搜寻。当初他能够很容易的反对每妙数千个文件拜访申请。如果必要的话,咱们能够再namespace数据结构前加一个name查找缓存来进一步减速。
6.2.5 复原工夫
在一个chunkserver失败了,在该server上的chunk的正本数就会低于要求,这时候就必须clone来地位失常正本数量。复原这些chunk的时候取决于资源的数量。在一个试验中,咱们在集群B杀死了一个chunkserver。这个chunkserver蕴含了15000个chunks差不多600G的数据。为了缩小复原对运行的利用的影响和为调度决策提供余地。咱们默认的参数限度集群并发的clone操作在91个(是chunkserver数据量的40%左右)。并且每个clone操作只容许生产最多6.25M/s(50Mbps)。所有的chunk在23.2分钟才被复原,备份速度在440M/s。
在另一个试验中,咱们杀死了两个chunkserver,每个有大略16000个chunk和660G数据。这个双失败让266个chunk变的只有一个正本了。所以这266个chunk在clone的时候有一个更高的优先级。咱们在两分钟内复原了这些正本至多2份。从而让集群能够容忍另外一个chunkserver如果也失败了不会有数据失落。
6.3 工作负载分析
这一节,咱们具体介绍了两个GFS群集上的工作负载细分,这两个集群与6.2节中相当但不完全相同。群集X用于研发,而群集Y用于生产数据处理。
6.3.1 办法和阐明
这些后果只蕴含客户端发动的申请,因为他们能够反映由咱们应用程序对整个文件系统产生的工作负载。他们不包含为了执行客户端的申请或者服务器外部的后盾流动(比方写推送或者重均衡)而在服务器外部的申请。
IO操作的统计数据是从咱们在GFS上实在的RPC申请日志记录信息从新构建而来的。比方,GFS客户端的代码可能将一个读申请拆分了多个RPC申请来进步并行度,咱们就通过日志推断出他的原始申请。因为咱们的拜访模式是程式化的,咱们心愿任何的谬误都能够呈现在日志中。应用程序显式的日志可能能够提供更精准的数据,然而从新编译和启动数千个在运行的客户端来做这个日志统计操作在逻辑上是不可能的,从那么多机器上收集信息是非常的轻便的。
须要留神的一点是,不要去适度的概括咱们的工作负载。因为GFS和应用程序都是由google齐全管制的,应用程序偏向于为GFS调整本人,而相同的GFS是为这些利用程序设计的。这种互相的作用可能也存在一些宽泛的文件系统和应用程序中,然而这种影响在咱们的case中可能更加显著。
6.3.2 chunkserver负载
表4展现依据操作的大小的散布数据。读操作展现出了一个双峰分布。小量的读(低于64kb的)来自于那些大量文件中查找一些小片数据的随机读的客户端。大量读的操作(超过512kb)来自于那些须要长时间线性读取整个文件的程序。
对于集群Y有大量的读操作没有返回任何数据。咱们的程序,特地是那些生产零碎,常常应用文件来作为生产者和消费者队列。生产者并发的向一个文件append数据而消费者从文件的境地读取文件。有时候,消费者的生产超过了生产者就不会返回数据。集群X显示其很少呈现这种状况,因为他通常是用来做短期的数据分析工作而不是长期的分布式应用程序。
写的规模也显示出双峰分布。大的写操作(大于256K)通常是来自于写操作者的缓冲。那些缓冲很少数据的写操作者,通常是检查点或者是同步操作,也或者是那些占用较小写入量(小于64Kb)的产生较少数据的写操作者。
随着记录的append,能够发现集群Y比X有更大的record append比率。因为咱们应用Y的那个生产零碎针对GFS做了更多的优化。
表5显示了数据在各种操作规模下的数据传输总量。对于所有品种的操作,大的操作(超过256KB)通常占传输的大多数字节。因为随机查找工作量须要,小读取(小于64 KB)确实只传输小量数据,但占了读操作的一大部分。
6.3.3 append与write
记录的append操作在咱们生产零碎中被大量应用。对于集群X,按字节传输来算write和append的比例是108:1,依照操作次数计算他们比例是8:1。对于集群Y,比例别离是3.7:1和2.5:1。一次,这些比例通知咱们两个集群他们的append操作都会比write操作要大。对于X,测量期间记录append操作整体要低,这可能是因为一两个有非凡缓冲大小设置的程序导致的后果歪斜。
正如冀望的,咱们数据的变更操作的负载是由append操作控制的而不是重写管制的。咱们测量了在主正本上的数据重写数量,这个数量很靠近那些客户端特地去重写先前写入的数据而不是追加新数据的小局部案例数量。对于集群X,以字节大小计算的话重写大略占了整个数据变更的0.0001%,以操作个数计算,大略小于0.0003%。对于Y集群,这两个比例都是0.05%,只管这也不算大,然而还是要高于咱们的冀望。后果发现大部分重写的操作都是来因为谬误或者超时客户端的重试操作。他们不算是工作负载的一部分,而是重试机制的后果。
6.3.4 master负载
~~~~
表6展现对master各种申请类型的拆分。大部分的申请都是为了读查问chunk的地位以及数据变更来获取租约持有信息的。(找chunk地位和获取租约锁)
集群X和集群Y能够看见在删除数量上的显著差异,因为集群Y存储的生产数据会定期的从新生产并替换为更新的版本。其中还有一些差别暗藏在open申请中,因为老版本的文件可能在被从新关上的时候给隐式删除(unix关上模式的'w'操作)。
findMatchingFiles是一个反对ls和相似文件系统操作的匹配模式。不同于其余的master的申请,他可能要解决大部分的命名空间,所以他的操作是很低廉的。集群Y更经常出现这种申请因为主动解决数据的工作更偏向于查看文件系统的各个局部以理解应用程序全局的状态。相同,集群X的程序更显著处于用户的管制下,所以很多操作都事后晓得了所须要的文件的名字(用户本人决定的)。
7.教训
在构建和部署GFS的过程中,咱们经验了大量的问题,一些是操作性的和一些是技术性的。
起初,GFS只是被构思用于咱们的生产零碎的后盾文件系统。随着工夫的推移,该用处进化为钻研和开发工作所应用。开始的他还不反对如权限,配额等这些货色,然而当初他们曾经根本都蕴含了。然而生产零碎是能够被很好的管制和训练的,然而用户的操作就不是了,所以咱们须要跟多的根底设置来防止用户和用户间的相互烦扰。
咱们一些最大的问题是磁盘和linux相干的。很多咱们的硬盘都宣称反对肯定范畴的IDE协定版本的linux驱动,但实际上依据反馈来看他们是在最新的一些版本上是可以信赖的。因为协定的版本都很类似,所以这些驱动大部分是能够失常工作的,然而某些场合下,这种不匹配会让驱动和内核在硬盘驱动状态下产生分歧。这会因为内核的谬误导致数据默默地被净化了。这个问题激发了咱们应用校验和来检测数据净化,当产生这种状况,咱们就须要去批改内核来解决这种协定不匹配的问题。
新近,咱们应用linux2.2内核的时候发现了一些因为fsync()函数的破费导致的问题。他的破费是和文件的大小而不是被批改的局部成比例减少的。这对于咱们大量的操作日志是一个问题,特地是在咱们实现检查点之前。咱们过后通过应用同步写来绕过这个问题,最初迁徙到linux2.4解决了这个问题。
另一个linux问题是:对于一个读写锁,在一个地址空间的任意线程在从磁盘中读页数据(读锁)或者在mmap()办法调用中批改地址空间(写锁)都必须持有一个读写锁。咱们发现在零碎在零碎在轻负载下短暂超时,并曾致力寻找是否是资源瓶颈或零星的硬件故障。最终,咱们发现这个读写锁在读磁盘线程在解决后面map的数据的时候,会阻塞主网络线程将新的数据映射到内存中。因为咱们的工作瓶颈次要在于网络带宽而不是内存拷贝带宽,咱们多破费一额定的copy操作应用pread()替换了mmap来绕过这个问题。
只管有一些问题,linux的可用性有助于咱们摸索和了解零碎的行为。在失当的时候,咱们也会改良内核并和开源社区分享这些变动。
8.相干工作
像其余的大型分布式文件系统比方AFS[5],GFS提供了一个本地独立的namespace,让数据能够为了负载平衡或者容错而通明的挪动。不同于AFS,为了晋升整体的性能和容错能力,GFS将文件数据在多个存储服务器上存储,这点更相似于xFS[1]或者Swift[3]。
因为硬盘绝对便宜,且与简单的RAID策略相比,应用正本策略更简略就能够。因为GFS以后采纳正本只是进行冗余因而它会比xFS或者Swift耗费更多的原始存储。
与AFS,xFS,Frangipani,Intermezzo这些零碎相比,GFS在文件系统接口下并不提供任何缓存。咱们的指标工作的负载类型对于单利用程序运行很少是可重用的,因为他们要么流式的读取大量数据集或者在外面进行随机的seek,而每次只读大量的数据。
一些如xFS,Frangipani,Minnesota’s GFS和GPFS的分布式文件系统删除了地方服务节点,依赖于分布式的算法来实现一致性和治理。咱们抉择地方化这个办法是为了简化设计,减少可靠性,获取灵活性。尤其是,一个地方化的master更容易实现简单的chunk搁置和备份策略,因为master曾经具备大部分的相干信息来管制了它们如何扭转。咱们通过让master状态很小以及在其余机器上进行备份来解决容错。咱们的影子master机制当期提供了可扩展性和可用性(对于读)。对于master状态的更新,会append到write-ahead这样的日志里来进行长久化。因而咱们能够通过相似于Harp里的primary-copy模式来提供一个比咱们以后模式具备更强一致性的高可用性保障。
咱们将来将解决相似于Lustre的一个问题:向大量客户端提供整体性能。然而咱们通过重视于咱们本人的需要而不是构建一个POSIX兼容文件系统来简化了这个问题。另外,GFS假如大量的组件都是不牢靠的,因而容错是咱们设计的核心。
GFS非常相似于NASD架构。只是NASD是基于网络连接的硬盘驱动器,而GFS则应用一般机器作为chunkserver,就像在NASD原型中所做的那样。与NASD工作不同的是,咱们的chunkserver在须要时调配固定大小的chunk而不是变长的对象。此外,GFS还实现了诸如重均衡,正本,疾速复原这些在生产环境须要的机制。
不像Minnesota’s GFS和NASD,咱们并没有寻求扭转存储设备的模型。咱们更专一应用现有商品化组件来组成的简单分布式系统去解决日常的数据处理需要。
生产者消费者队列应用原子record append操作解决了与分布式队列-River一个类似的问题。River应用跨多机器基于内存的分布式队列以及认真的数据流管制来解决这个问题,而GFS只应用了一个能够被很多生产者并发地进行append的长久化文件。River模型反对m对n的分布式队列,但不足长久化存储带来的容错力,而GFS只能高效地反对m-对-1队列。多个消费者能够读取雷同文件,然而它们必须协调来划分传进的负载。
9.总结
谷歌文件系统展现了在一般硬件上反对大规模数据处理工作负载的因素。只管一些设计决定是针对咱们非凡设置的,然而大部分能够利用在一个相似规模和老本意识的数据处理工作。
依据咱们以后和预期的应用程序工作负载和技术环境,咱们从新扫视传统的文件系统的一些假如。通过观察咱们的在设计中有一些与传统设计基本上不同的观点:咱们将组件失败看做常态而不是异样,为常常进行的在大文件上的append进行优化(可能是并发的),而后是读(通常是程序的),扩大并且放松了规范文件系统接口来改良整个零碎。
咱们通过一直的监控,备份要害数据,疾速和主动复原来提供零碎的容错。Chunk备份让咱们能够容忍chunkserver的失败产生。这些经常性的失败,驱动了一个优雅的在线修复机制的产生,它尽可能快的周期性通明的修复那些失落的正本。另外,咱们通过应用校验和来检测磁盘或者IDE子系统级别的数据损坏,对于零碎中硬盘数目很大的时候,这种损坏就变得很失常。
咱们的设计对于很多执行大量工作的并发读者和写者实现了高的整体吞吐率。咱们这是通过拆散文件系统管制实现的,这个管制让master间接来解决,对于数据传输则在chunkserver和客户端之间进行。通过增大chunk的大小以及chunk的租约机制,缩小master在一般操作中的参与度。这让一个简略的,地方化的的master不会成为瓶颈成为可能。咱们置信网络协议栈的改良将解除以后对单个客户端看到的写入吞吐量的限度。
GFS胜利地满足了咱们的存储需要,并宽泛的被作为一个数据存储平台用于google的钻研和开发以及生产数据的解决。它是一个重要的工具,使咱们可能在整个互联网持续翻新和解决问题。
致谢
We wish to thank the following people for their contributions to the system or the paper. Brain Bershad (our shepherd) and the anonymous reviewers gave us valuable comments and suggestions. Anurag Acharya, Jeff Dean, and David des- Jardins contributed to the early design. Fay Chang worked on comparison of replicas across chunkservers. Guy Ed- jlali worked on storage quota. Markus Gutschke worked on a testing framework and security enhancements. David Kramer worked on performance enhancements. Fay Chang, Urs Hoelzle, Max Ibel, Sharon Perl, Rob Pike, and Debby Wallach commented on earlier drafts of the paper. Many of our colleagues at Google bravely trusted their data to a new file system and gave us useful feedback. Yoshka helped with early testing.
援用
[1] Thomas Anderson, Michael Dahlin, Jeanna Neefe, David Patterson, Drew Roselli, and Randolph Wang. Serverless network file systems. In Proceedings of the 15th ACM Symposium on Operating System Principles, pages 109–126, Copper Mountain Resort, Colorado, December 1995.
[2] Remzi H. Arpaci-Dusseau, Eric Anderson, Noah Treuhaft, David E. Culler, Joseph M. Hellerstein, David Patterson, and Kathy Yelick. Cluster I/O with River: Making the fast case common. In Proceedings of the Sixth Workshop on Input/Output in Parallel and Distributed Systems (IOPADS ’99), pages 10–22, Atlanta, Georgia, May 1999.
[3] Luis-Felipe Cabrera and Darrell D. E. Long. Swift: Using distributed disk striping to provide high I/O data rates. Computer Systems, 4(4):405–436, 1991.
[4] Garth A. Gibson, David F. Nagle, Khalil Amiri, Jeff Butler, Fay W. Chang, Howard Gobioff, Charles Hardin, Erik Riedel, David Rochberg, and Jim Zelenka. A cost-effective, high-bandwidth storage architecture. In Proceedings of the 8th Architectural Support for Programming Languages and Operating Systems, pages 92–103, San Jose, California, October 1998.
[5] John Howard, Michael Kazar, Sherri Menees, David Nichols, Mahadev Satyanarayanan, Robert Sidebotham, and Michael West. Scale and performance in a distributed file system. ACM Transactions on Computer Systems, 6(1):51–81, February 1988.
[6] InterMezzo. http://www.inter-mezzo.org, 2003.
[7] Barbara Liskov, Sanjay Ghemawat, Robert Gruber, Paul Johnson, Liuba Shrira, and Michael Williams.
Replication in the Harp file system. In 13th Symposium on Operating System Principles, pages 226–238, Pacific Grove, CA, October 1991.
[8] Lustre. http://www.lustreorg, 2003.
[9] David A. Patterson, Garth A. Gibson, and Randy H.
Katz. A case for redundant arrays of inexpensive disks (RAID). In Proceedings of the 1988 ACM SIGMOD International Conference on Management of Data, pages 109–116, Chicago, Illinois, September 1988.
[10] Frank Schmuck and Roger Haskin. GPFS: A shared-disk file system for large computing clusters. In Proceedings of the First USENIX Conference on File and Storage Technologies, pages 231–244, Monterey, California, January 2002.
[11] Steven R. Soltis, Thomas M. Ruwart, and Matthew T. O’Keefe. The Gobal File System. In Proceedings of the Fifth NASA Goddard Space Flight Center Conference on Mass Storage Systems and Technologies, College Park, Maryland, September 1996.
[12] Chandramohan A. Thekkath, Timothy Mann, and Edward K. Lee. Frangipani: A scalable distributed file system. In Proceedings of the 16th ACM Symposium on Operating System Principles, pages 224–237, Saint-Malo, France, October 1997.