共计 34197 个字符,预计需要花费 86 分钟才能阅读完成。
该 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.