Google文件系统摘要我们设计并实现了Google文件系统,一个为数据中心的大规模分布应用设计的可伸缩的分布文件系统。Google文件系统虽然运行在廉价的普遍硬件上,但是可以提供容错能力,为大量客户机提供高性能的服务。我们的系统与许多以前的分布文件系统拥有许多相同的目标,但我们的设计还受到我们对我们的应用负载和技术环境观察的影响,不管现在还是将来,我们和早期文件系统的假设都有明显的不同。所以我们重新审视了传统的选择,发展了完全不同的设计观点。Google文件系统成功的满足了我们的存储需求。它作为存储平台被广泛的部署在Google内部,用在我们的服务中产生和处理数据,还用于那些需要大规模数据集的研究和开发。目前为止最大的集群利用数千台机器内的数千个硬盘,提供了数百T的存储空间,同时为数百个客户机服务。在这篇论文中,我们展现如果用文件系统接口扩展设计去支撑分布应用,讨论我们设计的许多方面,最后报告在小规模性能测试以及真实世界中系统的性能测试结果。1.简介我们设计并实现了Google文件系统(GoogleFileSystem-GFS),用来满足Google迅速增长的数据处理需求。GFS与过去的分布文件系统拥有许多相同的目标,例如性能,可伸缩性,可靠性以及可用性。然而,它的设计还受到我们对我们的应用负载和技术环境观察的影响,不管现在还是将来,我们和早期文件系统的假设都有明显的不同。所以我们重新审视了传统的选择,采取了完全不同的设计观点。首先,组件失效不再被认为是意外,而是被看做正常的现象。这个文件系统包括几百甚至几千台普通廉价部件构成的存储机器,又被相应数量的客户机访问。组件的数量和质量几乎保证,在任何给定时间,某些组件无法工作,而某些组件无法从他们的目前的失效状态恢复。我们发现过,应用程序bug造成的问题,操作系统bug造成的问题,人为原因造成的问题,甚至硬盘、内存、连接器、网络以及电源失效造成的问题。所以,常量监视器,错误侦测,容错以及自动恢复系统必须集成在系统中。其次,按照传统的标准来看,我们的文件非常巨大。数G的文件非常寻常。每个文件通常包含许多应用程序对象,比如web文档。传统情况下快速增长的数据集在容量达到数T,对象数达到数亿的时候,即使文件系统支持,处理数据集的方式也就是笨拙地管理数亿KB尺寸的小文件。所以,设计预期和参数,例如I/O操作和块尺寸都要重新考虑。第三,在Google大部分文件的修改,不是覆盖原有数据,而是在文件尾追加新数据。对文件的随机写是几乎不存在的。一般写入后,文件就只会被读,而且通常是按顺序读。很多种数据都有这些特性。有些数据构成数据仓库供数据分析程序扫描。有些数据是运行的程序连续生成的数据流。有些是存档的数据。有些数据是在一台机器生成,在另外一台机器处理的中间数据。对于这类巨大文件的访问模式,客户端对数据块缓存失去了意义,追加操作成为性能优化和原子性保证的焦点。第四,应用程序和文件系统API的协同设计提高了整个系统的灵活性。例如,我们放松了对GFS一致性模型的要求,这样不用加重应用程序的负担,就大大的简化了文件系统的设计。我们还引入了原子性的追加操作,这样多个客户端同时进行追加的时候,就不需要额外的同步操作了。论文后面还会对这些问题的细节进行讨论。为了不同的应用,Google已经部署了许多GFS集群。最大的一个,拥有超过1000个存储节点,超过300T的硬盘空间,被不同机器上的数百个客户端连续不断的频繁访问着。2.设计概述2.1设计预期在设计我们需要的文件系统时,我们用既有机会又有挑战的设计预期来指导我们的工作。前面我们提到了一些关键的信息,这里我们讲更细节的描述我们的设计预期。这个系统由许多廉价易损的普通组件组成。它必须持续监视自己的状态,它必须在组件失效作为一种常态的情况下,迅速地侦测、承担并恢复那些组件失效。这个系统保存一定数量的大文件。我们预期有几百万文件,尺寸通常是100MB或者以上。数GB的文件也很寻常,而且被有效的管理。小文件必须支持,但是不需要去优化。负载中主要包含两种读操作:大规模的流式读取和小规模随机读取。大规模的流式读取通常一次操作就读取数百K数据,更常见的是一次性读取1MB甚至等多。同一个客户机的连续操作通常是对一个文件的某个区域进行连续读取。小规模的随机读取通常是在随机的位置读取几个KB。对性能有所要求的程序通常把小规模的读批量处理并且排序,这样就不需要对文件进行时前时后的读取,提高了对文件读取的顺序性。负载中还包括许多大规模的顺序的写操作,追加数据到文件尾部。一般来说这些写操作跟大规模读的尺寸类似。数据一旦被写入,文件就几乎不会被修改了。系统对文件的随机位置写入操作是支持的,但是不必有效率。系统必须高效的实现良好定义的多客户端并行追加到一个文件的语意。我们的文件经常用于生产者-消费者队列,或者多路文件合并。数百个生产者,一个机器一个,同时的对一个文件进行追加。用最小的同步开销实现追加的原子操作是非常重要的。文件可能稍后被读取,也可能一个消费者同步的读取文件。高度可用的带宽比低延迟更加重要。大多数我们的目标程序,在高传输速率下,大块地操作数据,因为大部分单独的读写操作没有严格的响应时间要求。2.2接口GFS提供了一个类似传统文件系统的接口,虽然它并没有实现类似POSIX的标准API。文件在目录中按照层次组织,用路径名来标识。我们支持常用的操作,如创建,删除,打开,关闭,读和写文件。而且,GFS有快照和记录追加操作。快照操作可以用很低的成本创建文件或者目录树的拷贝。记录追加操作可以在保证原子性的前提下,允许多个客户端同时在一个文件上追加数据。这对于实现多路结果合并以及生产者-消费者模型非常有好处,多个客户端可以同时在一个文件上追加数据,而不需要任何额外的锁定。我们发现这些文件类型对构建大型分布应用是非常有价值的。快照和记录追加将分别在3.4和3.3章节讨论。2.3架构一个GFS集群包含一个主服务器和多个块服务器,被多个客户端访问,如图1。这些机器通常都是普通的Linux机器,运行着一个基于用户层的服务进程。如果机器的资源允许,而且运行多个程序带来的低稳定性是可以接受的话,我们可以很简单的把块服务器和客户端运行在同一台机器。图1:GFS架构文件被分割成固定尺寸的块。在每个块创建的时候,服务器分配给它一个不变的、全球唯一的64位的块句柄对它进行标识。块服务器把块作为linux文件保存在本地硬盘上,并根据指定的块句柄和字节范围来读写块数据。为了保证可靠性,每个块都会复制到多个块服务器上。缺省情况下,我们保存三个备份,不过用户可以为不同的文件命名空间设定不同的复制级别。主服务器管理文件系统所有的元数据。这包括名称空间,访问控制信息,文件到块的映射信息,以及块当前所在的位置。它还管理系统范围的活动,例如块租用管理,孤儿块的垃圾回收,以及块在块服务器间的移动。主服务器用心跳信息周期地跟每个块服务器通讯,给他们以指示并收集他们的状态。GFS客户端代码被嵌入到每个程序里,它实现了Google文件系统API,帮助应用程序与主服务器和块服务器通讯,对数据进行读写。客户端跟主服务器交互进行元数据操作,但是所有的数据操作的通讯都是直接和块服务器进行的。我们并不提供POSIXAPI,而且调用不需要深入到Linux的vnode级别。不管是客户端还是块服务器都不缓存文件数据。客户端缓存几乎没什么好处,因为大部分程序读取巨大文件的全部,或者工作集太大无法被缓存。不进行缓存简化了客户端和整个系统,因为无需考虑缓存相关的问题。(不过,客户端会缓存元数据。)块服务器不需要缓存文件数据的原因是,块保存成本地文件形式,Linux的缓冲器会把经常被访问的数据缓存在内存中。2.4单一主服务器单一的主服务器大大简化了我们的设计,这样主服务器可以通过全局的信息精确确定块的位置以及进行复制决定。然而,我们必须减少主服务器对数据读写的影响,避免使主服务器成为系统的瓶颈。客户端不通过主服务器读写数据。反之,客户端向主服务器询问它应该联系的块服务器。客户端短期缓存这些信息,后续的操作直接跟块服务器进行。让我们解释下图一表示的一次简单读取的流程。首先,利用固定的块尺寸,客户端把文件名和程序指定的字节偏移转换成文件的块索引。然后,它把文件名和块索引发送给主服务器。主服务器回答相应的块句柄和副本们的位置。客户端用文件名和块索引作为键值缓存这些信息。然后客户端发送请求到其中的有一个副本处,一般会选择最近的。这个请求指定了块的块句柄和字节范围。对同一块的更多读取不需要客户端和主服务器通讯,除非缓存的信息过期或者文件被重新打开。实际上,客户端通常在一次请求中查询多个块,而主服务器的回应也可以包含紧跟着这些请求块后面的块的信息。这些额外的信息实际上,在没有代价的前提下,避免了客户端和服务器未来的几次通讯。2.5块尺寸块尺寸是关键设计参数之一。我们选择了64MB,这远大于一般文件系统的块尺寸。每个块副本保存在块服务器上的Linux文件内,尺寸只在需要的时候才扩展变大。惰性空间分配避免了造成内部碎片带来的空间浪费,或许对巨大的块尺寸最没有争议的就是这点。巨大的块尺寸有几个重要的好处。首先,它减少了客户端和主服务器通讯的需求,因为对同一个块的读写,只需要一次用于获得块位置信息的与主服务器的通讯。对我们的工作负载来说,这种减少尤其明显,因为我们的应用程序经常连续读写巨大的文件。即使是对小规模随机读取也有好处,因为客户端也可以轻松的缓存一个数TB工具集的所有块位置。其次,由于块尺寸很大,所以客户端会对一个给定的块进行许多操作,这样就可以通过跟块服务器保持较长时间的TCP连接来减少网络负载。第三,它降低了主服务器需要保存的元数据的尺寸。这就允许我们把元数据放在内存中,这样会带来一些其他的好处,我们将在2.6.1章节讨论。另一方面,巨大的块尺寸,即使配合惰性空间分配,也有它的缺点。小文件包含较少的块,甚至可能是一块。如果有许多的客户端访问同一个小文件,那么存储这些块的块服务器就会变成热点。在实践中,热点没有成为主要的问题,因为我们的程序通常是连续的读取多个块的大文件。然而,在GPS第一次用于一个批处理队列系统的时候,热点还是产生了:一个执行文件被写为GFS的一个单块文件,然后在数百个机器上同时启动。保存这个执行文件的几个块服务器被数百个并发请求访问到过载的地步。我们解决了这个问题,方法是用更高的复制参数来保存这类的执行文件,以及让批处理队列系统错开程序的启动时间。一个可能的长期解决方案是,在这样的情况下,允许客户端从其他客户端读取数据。(译者注:这个热点事件,其实就是一个典型的文件分发问题,而后面提到的所谓可能的长期解决方案其实就是p2p。)2.6元数据主服务器保存三种主要类型的元数据:文件和块的命名空间,文件到块的映射,以及每个块副本的位置。所有的元数据都保存在主服务器的内存里。除此之外,前两种类型(命名空间和文件块映射)的元数据,还会用日志的方式保存在主服务器的硬盘上的操作日志内,并在远程的机器内复制一个副本。使用日志,让我们可以简单可靠的更新主服务器的状态,而且不用担心服务器崩溃带来的数据不一致的风险。主服务器不会持久化保存块的位置信息。主服务器在自己启动以及块服务器加入集群的时候,询问块服务器它所包含的块的信息。2.6.1内存内数据结构因为元数据保存在内存中,所以主服务器操作的速度很快。而且,主服务器可以在后台,简单而高效的周期扫猫自己的整个状态。这种周期性的扫描用来在块服务器间实现块的垃圾收集的功能,用来实现块服务器失效的时复制新副本的功能,用来实现负载均衡的块移动的功能,以及用来实现统计硬盘使用情况的功能等。4.3和4.4章节将深入讨论这些行为。这种纯内存的机制一种可能的问题是,块的数量和整个系统的容量都受限于主服务器拥有的内存尺寸。不过实际上这不是一个严重的限制。对于每个64MB的块,服务器只需管理不到64字节的元数据。大多数的块是满的,因为每个文件只有最后一块是部分填充的。类似的,每个文件的命名空间通常在64字节