The Google File System 中文翻译版

The Google File System 中文翻译版

作者:

Sanjay Ghemawat, Howard Gobioff, and Shun-Tak Leung

原版的版权说明

1
2
3
4
5
6
7
8
Permission to make digital or hard copies of all or part of this work for
personal or classroom use is granted without fee provided that copies are
not made or distributed for profit or commercial advantage and that copies
bear this notice and the full citation on the first page. To copy otherwise, to
republish, to post on servers or to redistribute to lists, requires prior specific
permission and/or a fee.
SOSP’03, October 19–22, 2003, Bolton Landing, New York, USA.
Copyright 2003 ACM 1-58113-757-5/03/0010 ...$5.00.

摘要

我们设计并实现了谷歌文件系统,这是一个面向大规模数据密集型应用的、可伸缩的分布式文件系统。 GFS 虽然运行在廉价的硬件设备上,但是它依然了提供灾难冗余的能力,为大量客户机提供了高性能的服务。

虽然 GFS 与许多传统的分布式文件系统有很多共同的目标,但是由于我们是基于目前现有应用程序的负载和技术环境而驱动 GFS 设计的, 所以在目前和可以预见的以后,GFS 和早期的分布式文件系统有明显的不同。这促使我们重新审视传统文件系统,衍生出了完全不同的设计思路。

GFS 完全满足了我们对存储的需求。 GFS 已经被广泛的部署在 Google 内部作为存储平台,存放我们的服务产生和处理的数据,同时还用于那些需要大规模数据集的研究和开发工作。 最大的一个集群基于数千个服务器和数千个硬盘提供了数百 TB 的存储空间并同时为数百个客户端服务。

在此论文中,我们会展示为了支持分布式应用程序的文件系统扩展程序,讨论一些设计方面的内容, 并且提供一些小规模性能测试结果以及真实生产系统的测试结果。

1 引言

为了满足谷歌迅速增长的数据处理需求,我们设计并实现了谷歌文件系统(GFS)。 GFS 与传统的分布式文件系统有着很多相同的设计目标,比如,性能、可伸缩性、可靠性以及可用性。 但是由于我们是基于目前现有应用程序的负载和技术环境而驱动 GFS 设计的,所以在目前和可以预见的以后,GFS 和早期的分布式文件系统有明显的不同。 我们重新审视传统文件系统,衍生出了完全不同的设计思路。

第一,组件失效被认为是常态事件,而不是意外事件。 GFS 可能由数百甚至数千个使用廉价硬件设备而搭建的存储设备组成,它还能被相当数量的客户端访问。 GFS 组件的数量和质量导致在事实上,任何给定时间内都有可能发生某些组件无法工作,某些组件无法从它们目前的失效状态中恢复。 我们遇到过各种各样的问题,比如程序错误、系统错误、人为失误,甚至还有硬盘、内存、线材、网络以及电源造成的问题。 因此持续监控,错误检测,灾难冗余和自动恢复的机制必须集成在 GFS 中。

第二,文件相对于传统标准来说比较大。几个 GB 的文件是最常见的。每个文件通常包含有多个应用程序的对象,例如 Web 文档。 当我们经常需要处理快速增长的、并且由数亿个对象构成的、数以 TB 的数据集时,采用管理数亿个 KB 大小的小文件的方式是非常不明智的, 尽管有些文件系统支持这样的管理方式。 因此,设计的假设条件和参数,比如I/O操作和Block的尺寸都需要重新考虑。

第三,绝大部分文件的修改是采用在文件尾部追加数据,而不是覆盖原有数据的方式。 对文件的随机写入操作在实际中几乎不存在。 一旦写入完毕之后,对文件的操作就只有读,而且通常是按顺序读。 大量的数据符合这些特性。 比如有些数据可能会构成大型存储库,数据分析程序会扫描这些存储库。 有些是正在运行的应用程序生成的数据流; 有些是数据存档; 有些由一台机器生成、另外一台机器处理的中间数据,这些中间数据的处理可能是同时进行的、也可能是后续才处理的。 对于这种针对海量文件的访问模式,客户端对数据块缓存是没有意义的,数据的追加操作是性能优化和原子性保证的主要考量因素。

第四,应用程序和文件系统 API 的协同设计提高了整个系统的灵活性。 例如,我们放松了 GFS 的一致性模型,以极大地简化文件系统,而不会给应用程序带来繁重的负担。 我们引入了原子性的记录追加操作,从而保证多个客户端能够并行进行追加操作,不需要额外的同步操作来保证数据的一致性。 本文后面还有对这些问题的细节的详细讨论。

目前我们已经为了不同的需求搭建了多套 GFS 集群。 最大的集群有1000个存储节点,超过 300 TB 的存储空间,并且被不同机器上的数百个客户端连续不断的频繁访问。

2 设计概述

2.1 设计预期

在设计满足我们需求的文件系统时,无数的机遇和挑战指引着我们。 之前我们已经提到了一些需要关注的关键点,这里我们将对设计预期的细节展开讨论。

  • 该系统由许多经常发生故障的廉价组件组成。它必须不断地自我监控,定期检测,容忍错误并能从组件故障中迅速恢复。
  • 系统中存在一定数量大文件。我们推测将会有几百万个文件,每个文件的大小通常为 100 MB或更大。 数个 GB 的文件是常见情况,应进行有效管理。 此系统还必须支持小文件的存储,但是我们不需要对其进行优化。
  • 此系统的负载主要由以下两种读取方式构成:大型流式读取和小型随机读取。 在大型流式读取的过程中每个独立的操作通常读取几百 KB,更常见的是 1 MB 甚至更多。 来自同一客户端的连续操作通常会读取同一文件的连续区域。少量随机读取通常会以任意偏移量读取几个 KB。 如果应用程序对性能非常关注,通常的做法是把小规模的随机读取操作合并并排序,之后按顺序批量读取,这样就避免了在文件中前后来回的移动读取位置。
  • 在系统中的负载也有很多是大型有序的追加写入操作。在此过程中的操作大小和读取操作应该是类似的。一旦写入之后文件就不会经常变动了。 本系统还支持对文件的任意位置进行小型的写入操作,但是效率不一定高。
  • 系统必须高效的、行为定义明确的实现多客户端并行追加数据到同一个文件里。 我们的文件通常被用于生产-消费者队列中的合并操作。数百个生产者服务每个服务运行在一台设备上,同时对一个文件进行追加操作。 使用最小的同步开销来实现的原子的多路追加数据操作是必不可少的。 这个文件可能之后才会被读取或者一个消费者正在同时读取文件。
  • 高的持续带宽比低延迟更重要。通常我们的目标程序都非常重视以高速率处理大量数据,而很少有对单个读取或写入有严格响应时间要求的应用程序。

2.2 程序接口

GFS 提供了与文件系统类似的程序接口,虽然没有实现诸如 POSIX 之类的标准 API。 文件以分层目录的形式组织,用路径名来标识。 我们支持常用的操作,如创建新文件、删除文件、打开文件、关闭文件、读和写文件。

注: POSIX 维基百科

此外,GFS具有快照和记录追加操作。 快照可以花费很低的成本来创建一个文件或者目录树的拷贝。 记录追加可以同时把多个客户端的将数据追加到同一文件中,并且保证每个客户端的追加的原子性。 这对于实现多路结果合并,以及生产者-消费者队列非常有用,多个客户端可以在不需要额外的同步锁定的情况下,同时对一个文件追加数据。 我们发现这些类型的文件在构建大型分布式应用程序中具有不可估量的价值。 快照和记录追加操作将在3.4和3.3节分别讨论。

2.3 系统架构

图1 GFS 系统架构

GFS 集群由一个主节点,多个存储节点和访问他们的多个客户端构成,就像图1一样。 每一个组件都以用户级别的进程运行在普通的 Linux 设备上。 在机器资源允许的情况下,我们可以在同一台机器上同时运行存储服务和客户端程序,并且我们能够接受不可靠的应用程序代码带来的稳定性降低的风险。

文件会被分解成固定大小的文件块。每一个文件块在创建的时候都会被主节点分配一个不可变且全局唯一的 64bit 标识。 存储节点会把文件块当做 Linux 文件存放在本地磁盘上,并且会根据全局标识和操作的字节范围来读取或是写入这个文件块。 为了提高可靠性,每一个文件块都会被复制到多个服务器上。 我们默认会将文件存储三份,但是用户可以为不同命名空间下的不同区域指定复制的数量。

主节点维护着所有文件系统当中的元数据。元数据包含有文件的命名空间,访问控制信息,文件和块的映射关系以及文件块所处的位置。 主节点还控制着系统级别的操作,比方说文件块的管理,文件块的垃圾回收和文件块的移动。 主节点使用心跳信息周期地和每个存储节点通讯,并发送指令到各个存储节点并接收存储节点的状态信息。

GFS 客户端为应用程序实现了文件系统 API 来与主节点和存储节点通信实现读取或写入操作。 GFS 客户端只和主节点进行元数据方面的交互,具体的数据通信都是直接和存储节点实现的。 我们不提供 POSIX API,所以就不需要和 Linux 的 vnode 层有关系了。

注:要理解 vnode 可以先看看 inode

inode 维基百科 , vnode 文档

所有的 GFS 客户端和存储节点都是不缓存文件数据的。 客户端缓存文件几乎没有什么意义,因为大多数应用程序读取的都是很大的数据流或者说是数据集,这些内容根本没法缓存。 如果不做缓存也就没了缓存与文件不一致的问题,简化了客户端和整个系统。 (但是客户端还是缓存元数据的) 因为 Linux 的缓冲区缓存已经将经常访问的数据加载到了内存中而存储节点将文件块以本地文件的方式存储在硬盘上,所以数据存储节点也不需要实现文件缓存功能。

2.4 单个主节点

只有一个主节点可以极大的简化我们的设计,并且可以让主节点统筹全局的信息来制定杂的文件块存储和复制方案。 另外,我们必须减少对主节点的读写,避免主节点成为系统的瓶颈。 客户端并不通过主节点读写文件数据。 客户端只会向主节点查询它的要访问的存储节点。 客户端会在很小的一段时间内缓存这样的信息并直接与存储节点进行数据操作。

让我来利用 图1 解释一下一次简单的文件读取流程。 首先客户端会使用固定的分块大小将文件名和字节偏移量转化为分块索引。 然后客户端会把文件名和分块索引传送给主节点。 主节点会返回文件块的标识和副本的存储位置。 客户端利用文件名和分块索引作为 key 然后把主节点返回的内容进行缓存。

客户端通常会向最近的一个存储节点发送一次请求。 这个请求内会说明文件块的标识和大小。 在对这个文件块的后续操作过程中,客户端都不需要再和主节点进行通讯了,除非缓存过期或者说文件被重新打开了。 事实上,客户端通常会在一次请求中查询多个文件块的信息,主节点也可能会回应紧跟着这些文件块的其他文件块。 在实际应用中,这些额外的信息在没有任何代价的情况下,避免了客户端和主节点未来可能会发生的几次通讯。

2.5 文件块的大小

文件块的大小是一个重要的设计参数。 我们选择了 64 MB,这样的设计比一般的文件系统大很多。 每个文件块的副本都以普通 Linux 文件的形式保存在存储节点上,只有在需要的时候才进行扩大。 惰性空间分配策略避免了因内部碎片造成的空间浪费,内部碎片或许是对选择这么大的文件块最具争议一点。

增大文件块可以产生下面几个好处。 第一,可以减少客户端与主节点的交流次数,因为在单个文件块的情况下的读和写操作都只需要与主节点进行一次数据交互来读取文件块的存储位置。 这样减少主节点的负载对系统会产生明显的优化,因为大多数的应用程序是顺序读写大文件的。 第二,由于使用了大的文件块,客户机更可能在一个文件块上执行操作,因此它可以通过在一段较长的时间内保持与存储节点的 TCP 连接来减少网络开销。 第三,这样做允许我们把元数据存储在内存当中,这样做带来的好处我们会在 2.6.1 节讨论。

在另一方面,大的文件块和惰性空间分配也会有它们的缺点。 小文件由少量的文件块组成,或许只有一个。 如果许多客户机正在访问同一个文件,那么存储这些块的存储节点可能会变成热点。 在实际使用过程中热点问题并没有成为重大的问题,因为我们的应用程序通常在读取有序的多个文件块。

然而,当我们第一次把 GFS 用于批处理队列系统的时候,热点的问题还是产生了: 一个可执行文件在 GFS 上保存为单个文件块,之后这个可执行文件在数百台机器上同时启动。 存储这个可执行文件的几个存储节点被数百个并发请求访问过载。 我们提高了可执行文件的存储副本数量,并错开了批处理队列系统启动时间,解决了这个问题。 一个可能的长期解决方案是允许客户机在这种情况下从其他客户机读取数据。

2.6 元数据

主节点存储着以下三种主要的元数据:文件和文件块的命名空间,文件与文件块的映射关系,文件块的副本位置。 所有的元数据都储存在主节点的内存当中。 前两种类型的文件(命名空间和文件与文件块的映射关系)也会被持久化保存在变更日志中,而变更日志文件存储在本地磁盘上,同时日志会被复制到其他的远程主节点上。 使用日志的这种方式允许我们可以简单可靠的更新主节点状态,还可以避免在主节点崩溃时造成的一致性问题。 主节点不会持久化的保存文件块的位置。 反而,主节点会在启动的时候或者是集群新增存储节点的时候向存储节点查询它们存储的文件块。

2.6.1 内存中的数据结构

因为元数据保存在内存中,所以主节点的操作速度非常快。 并且,主节点以在后台简单而高效的周期性扫描自己保存的全部状态信息。 这种定期扫描用于实现文件块的垃圾回收,以及在存储服务器出现故障时重新复制文件块和文件块的迁移,平衡存储服务器之间的负载和磁盘空间。 第 4.3 和 4.4 节我们会详细讨论这些功能。

对于这种只使用内存的方式,一个潜在的问题是文件块的数量以及整个系统的容量受到主机内存量的限制。 这在实践中并不是一个严重的问题。 主节点中只需要 64 Bytes 的元数据就能管理一个文件块(64 MB)。 大部分的文件块都是满的因为大多数文件都由多个文件块组成,也就是说只有最后一个文件块可能有部分是空闲的。 同理,文件的命名空间通常也小于 64 Bytes ,因为保存的文件名是用压缩算法压缩过的。

即便是需要支持更大的文件系统,为主节点增加额外内存的费用是很少的,而通过增加有限的费用,我们就能够把元数据全部保存在内存里,增强了系统的简洁性、可靠性、高性能和灵活性。

2.6.2 文件块的存储位置

主节点并不会持久性的保存文件块副本与存储节点的对应关系。 主节点只会在启动时拉取存储节点的文件块存储信息。 此后,主节点可以保持自身的最新状态,因为它控制着所有文件块的位置,并使用常规心跳监视存储服务器的工作状态。

我们最初尝试过将文件块的信息持久化存储在主节点上,但是我们发现在启动之后或者说定期的从存储节点获取这些数据要简单的多。 这种设计简化了在存储节点加入,离开集群、更名、失效、以及重启的时候,主节点和存储节点数据同步的问题。 在集群中通常有数百台服务器,这样的问题经常发生。

可以从另外一个角度去理解这个设计决策:只有存储节点才能最终确定一个文件块是否在它的硬盘上。 试图在主节点上保持此信息的一致性是没有意义的,因为存储节点上的错误可能会导致文件块自动消失(例如,磁盘可能损坏并被禁用),或者操作员可能会重命名存储节点。

2.6.3 操作日志

操作日志中存储了重要的元数据变更记录。 这对 GFS 非常重要。 它不仅是元数据的唯一持久记录,而且它也作为判断同步操作顺序的逻辑时间基线。 文件和文件块和他们的版本(4.5 节),都由它们创建的逻辑时间唯一的、永久的标识。

操作日志非常关键,我们必须可靠地存储它,并且直到元数据修改完成并且持久化之后,日志对于客户端来说才是可见的。 否则的话,即使这些文件块本身仍然存在,我们仍然会失去整个文件系统或是客户端最近的操作。 因此,我们才会将操作日志复制到多台设备上,并且只有在日志完整写入磁盘然后同步至远程设备之后才会响应客户端的操作。 主节点会通过批量将一些记录一起写入磁盘的方式来减少写入磁盘和复制对系统整体性能的影响。

主节点也会通过重演操作日志的方式来完成灾难恢复。 为了减少启动时长,我们必须让操作日志尽肯能的少。 每当日志增长到超过一定大小时,主服务器就会检查其状态,以便存储节点可以通过从本地磁盘加载最新的检查点然后只需要重演少量的操作日志就可以完成恢复。 检查点采用了类似于 B树的紧凑形式,这种形式可以直接映射到内存当中,在用于命名空间查询时无需额外的解析。 这一特性进一步加快了恢复速度并且提高了可用性。

因为构建一个检查点需要花费一些时间,因此存储主机的内部状态的数据结构要能在不影响现有操作的前提下保存检查点。 主节点会切换到一个全新的日志文件当中并且使用单独的线程创建检查点。 新的检查点中会包含切换前的所有数据变化。 只需要一分钟就可以建立一个包含数百万个文件的检查点。 在检查点建立完成之后会写入本地和远程地址中。

只需要有最新的完整检查点和此检查点之后的日志我们就可以完成数据恢复了。 旧的检查点和日志文件可以被删除,但是为了应对灾难性的故障我们通常会多保存一些历史文件。 检查点建立时出现的故障并不会影响集群恢复,因为在恢复的时候会自动检测并跳过不完整的检查点。

2.7 一致性模型

GFS 使用了较为宽松的一致性模型,该模型可以很好地支持我们高度分散的应用程序,实现起来相对简单有效。 本节我们讨论 GFS 的一致性的保障机制,以及对应用程序的意义。 我们还将重点介绍 GFS 是如何维持一致性的,但是细节就留给了本文中的其他部分。

2.7.1 GFS 一致性保障机制

文件命名空间的变化(例如:创建文件)是原子性的。 它们仅由主节点控制: 命名空间锁提供了原子性和正确性(4.1 节); 主节点的操作日志定义了这些操作在全局的顺序(2.6.3 节)。

数据修改后区域的状态取决于操作的类型、成功与否、以及是否同步修改。 表1 总结了各种操作的结果。 如果所有客户端,无论从哪个副本读取,读到的数据都一样,那么我们认为文件的这一区域是一致的(consistent)。 如果对文件的数据修改之后,区域是一致的,并且客户端能够看到写入操作全部的内容,那么这个区域是已定义的(defined)。 当一个数据修改操作成功执行,并且没有受到同时执行的其他写入操作的干扰,那么影响的区域就是已定义的(隐含了一致性): 所有的客户端都可以看到写入的内容。 并行修改操作成功完成之后,区域处于一致的、未定义的状态:所有的客户端看到同样的数据,但是无法读到任何一次写入操作写入的数据。 通常情况下,区域内包含了来自多个修改操作的、混杂的数据片段。 失败的修改操作导致一个区域处于不一致状态(同时也是未定义的):不同的客户端在不同的时间会看到不同的内容。 后面我们将描述应用如何区分已定义和未定义的区域。 应用程序没有必要再去细分未定义区域的不同类型。

表1 文件 Region 状态变化

数据修改操作分为写入或者记录追加两种。 写入操作把数据写在应用程序指定的文件偏移位置上。 即使有多个修改操作并行执行时,记录追加操作至少可以把数据原子性的追加到文件中一次,但是偏移位置是由 GFS 选择的(3.3 节) (相比之下,“常规”追加只是客户端认为是文件当前结尾的偏移量处的写操作。) GFS 会返回给客户端偏移量,并且标记定义的数据的起始区域。 此外,GFS 可能会在文件中插入填充数据或重复记录。 这些数据占据的文件区域被认定是不一致的,这些数据通常比用户数据小的多。

经过了一系列的成功的修改操作之后,GFS 确保被修改的文件区域是已定义的,并且包含最后一次修改操作写入的数据。 GFS 通过以下手段确保上述结果: (a) 对文件块的所有副本采用相同的顺序进行修改操作。 (b) 利用文件块的版本号来侦测副本是否因为它所在的存储节点宕机(4.5 节)而错过了修改操作而导致其失效。 旧版本的文件块是不会被改变的,主节点也不会将它的地址返回给客户端。 它们会被垃圾回收机制尽早的回收掉。

因为客户端有可能会缓存文件块的存储位置,所以说可能会在数据刷新前客户端读取到了陈旧文件副本的问题, 这样的问题只会出现在缓存过期的时间和文件下一次被打开的时间的间隔形成的时间窗内,因为文件打开时会清除缓存中与该文件有关的所有文件块的位置信息。 而且,由于追加操作是最常见的操作行为,所以陈旧的副本通常返回的是缺失的数据而不是过时的数据。 当读取端重新联系主节点的时候,它将立即获得当前的文件块存储位置。

即使在修改操作成功执行很长时间之后,组件的失效也可能损坏或者删除数据。 GFS 通过主节点与所有存储节点之间的定期握手来识别故障的存储节点,并通过校验的方式检测数据损坏(5.2 节)。 一旦发现了问题,集群将尽快通过有效副本恢复数据(4.3 节)。 只有在 GFS 做出反应之前(通常在几分钟内),所有的副本都丢失了,才会不可逆的丢失文件块。 即使在这种情况下,文件块也只是不可用了而不是损坏了:应用程序会接收到明确的错误信息而不是损坏的数据。

2.7.2 程序的实现

使用 GFS 的应用程序可以利用一些简单技术实现这个宽松的一致性模型,这些技术也用来实现一些其他的目标功能,包括:尽量采用追加写入而不是覆盖,检查点和自验证的写入操作,自记录标识。

我们的大多数应用程序都是通过追加而不是覆盖来改变文件内容。 典型的使用方式是从头到尾编写文件。 在写入操作完成后应用程序会原子性的设置一个永久保存的文件名,或者定期建立检查点确保成功写入了多少数据。 检查点中还可能包含有应用程序级别的校验和。 读取器仅验证和处理直到最后一个检查点的文件区域,我们知道该检查点有一定处于已定义状态。 无论一致性和并发性有怎么样的问题,这种方法都为我们提供了很好的服务。 与随机读写相比追加写入的方式有着更好的效率,并且对程序失败的处理会更有弹性。 检查点这种机制允许写入端逐渐的完成启动操作,并且阻止读取端处理应用程序看来已经成功写入但是尚未完成全部操作的数据。

另一种典型的使用方式是,将许多写入端并发的追加写入同一个文件,通过这种方式来合并结果或者说实现生产者-消费者队列。 追加写入至少写入文件一次的语义可以保证写入端的输出。 读取端通过下面的方式来处理偶然的填充数据和重复内容。 在写入端编写的每条记录中都包含有校验和之类的额外内容,通过这种方式可以验证数据的有效性。 读取端可以使用校验和来识别并丢弃填充数据。 如果不能接受偶尔的数据重复的话(例如:如果他们会触发非幂等的操作),则可以使用记录中的唯一标识符号对数据进行过滤,唯一标识符也经常被命名程序中用于处理的实体对象,例如 web 文档。 这些功能(除了剔除重复数据)都被放在了库,并且适用于谷歌内部的其他的文件接口实现。 这样一来,相同顺序的记录加上较为罕见的重复数据就会被稳定的提供给读取端。

3 系统交互

此系统的设计主旨在于减少主节点在所有操作当中的参与程度。 在此背景下,我们现在来描述下 客户端,主节点和存储节点如何进行交互完成 变更数据,原子性的记录追加以及快照功能。

3.1 租约和变更顺序

数据变更是一个会改变文件块内容或者元数据的操作,比如写入操作或者记录追加操作。 每一次的数据变更都会执行在所有此文件块的副本中。 我们使用租约来维持多个副本之间数据变更顺序的一致性。 主节点会将租约授予给一个副本,我们将这个副本成为主副本。 主副本会文件块为指定数据变更的执行顺序。 所有的其他副本会根据这个顺序实现数据变更操作。 因此,修改操作全局的顺序首先由主节点选择的租约的顺序决定,然后由租约中主副本分配的序列号决定。

租约机制旨在于最大程度上减少主节点在数据管理上的开销。 租约的过期时间被设定为了默认60秒。 但是如果文件块发生了数据变更,那主副本就可以向主节点无限期的重新申请延长租约。 这些扩展请求会被装载在主机和存储节点定期交换的心跳记录上。 主节点有时也会尝试在租约到期之前撤销租约(例如:主机希望终止正在重命名的文件上执行的文件变更)。 即使主服务器和主副本失去通信,它们也在租约到期后安全的将新租约授予另一个副本。

在图2 中,我们遵循如下的步骤,展现写入操作的控制流程。

图2 写入操作的控制和数据流

  1. 客户端向主节点查询哪个存储节点持有该块的当前租约以及其他副本的位置。如果没有租约则主节点将选择一个副本授予租约。(未标明)
  2. 主节点将主副本的标识和位置和其他副本(次要副本)的位置返回给客户端。客户端缓存这些数据,并在以后用于数据变更。仅当主节点无法访问或者不再回复租约时,它才需要再与主节点产生联系。
  3. 客户端将数据推送到所有副本。客户端可以按照任何顺序进行上述操作。每个存储节点会将数据存储在内部 LRU 缓存区的高速缓存中直到这些数据使用完成或者缓存过期为止。通过将数据流与控制流分离,我们可以通过基于网络拓扑调度昂贵的数据流来提高性能,而不用关注哪个存储节点是主要的。3.2 节将对此进行进一步讨论。
  4. 一旦所有副本都确认已接收到数据,客户端就会向主副本发送写请求。这个请求标识了早前推送到所有副本的数据。主副本会为接收到所有的数据变化分配连续的序列号,这些操作可能来自不同的客户机,序列号保证了操作顺序执行。然后主副本会按照序列号来顺序执行变更操作改变文件状态。
  5. 主副本将写入请求转发至所有其他副本。每个其他副本都依据主副本的序列号采用相同的顺序文件变更。
  6. 其他副本回复主副本,表示依据完成了操作。
  7. 主副本答复其他副本。在这个过程中副本遇到的任何问题都要汇报给客户端。在出现错误的情况下,写入操作可能在主符合和一些其他副本执行成功。(如果操作在主副本上失败了,操作就不会被分配序列号,也不会被传递。)客户端的请求被确认为失败之后,被修改的区域会处于不一致的状态。我们的客户代码通过重试失败的数据变更来处理此类错误。在从头开始重复执行之前,客户端会针对步骤 3 到步骤 7 执行少量的重试操作。

如果应用程序发起的写入操作很大或者说跨越了文件块的便捷,GFS 客户端代码会将其分解为多个写操作。 它们都遵循上述控制流程,但可能与其他客户端的并发操作交错并被其覆盖。 因此共享的文件区域可能最终包含来自不同客户端的数据片段,因为每个操作在所有副本上都以相同的顺序正确完后程了,所以这些副本将是相同的。 如 2.7 节所述,这会使文件区域处于一致但未定义的状态。

3.2 数据流

我们将数据流与控制流分离开来,以有效地利用网络。 当控制流从客户端流向主副本,然后流至所有其他副本时,数据将以流的方式沿着精心挑选的存储节点链进行线性推送。 我们的目标是充分利用每台计算机的网络带宽,避免出现网络瓶颈和高延迟链接,并最小化推送所有数据的延迟。

为了充分利用每台机器的网络带宽,数据会沿着存储节点链进行线性推送,而不是以其他拓扑结构(例如,树型结构)。 因此,每台机器的全部出口带宽用于尽可能快地传输数据,而不是在多个接收者之间分配带宽。

为了尽可能避免网络瓶颈和高延迟链接(例如,交换机间链接经常同时出现),每台计算机都会将数据转发到尚未接收到数据的网络拓扑中“最近”的计算机。 假设客户端将数据推送到存储节点 S1 至 S4。 它将数据发送到最近的存储节点,例如 S1。 S1 通过最接近 S1 的 S4(例如S2) 将其转发到最接近的存储节点 S2。 同样,S2 将其转发到 S3 或 S4,以更接近 S2 的那个为准,依此类推。 我们的网络拓扑非常简单,可以从 IP 地址准确估计“距离”。

最后我们使用流水化的作业模式将数据通过 TCP 连接进行传输并以此来最大程度的减少延迟。 一旦存储节点接受到一些数据,它将立刻开始转发。 流水线的工作模式对我们来说特别有用,因为我们使用具有全双工连接的交换网络。 立即发送数据不会降低接收率。 在没有网络拥塞的情况下,将 B 字节传输到 R 副本的理想时间是 B/T+RL,其中 T 是 网络吞吐量,L 是在两台机器之间传输字节的等待时间。 我们的网络链路通常为 100 Mbps(T),L 远远低于 1 ms。 因此,理想情况下,可以在大约 80 毫秒内分配 1 MB。

3.3 原子性的数据追加

GFS 提供了一个原子性的追加操作称为记录追加。 在传统的写入操作中,客户端需要指定写入数据的偏移量。 但是并发向同一个区域中进行写入是不可以被序列化实现的:该区域可能会包含来自多个客户端的数据片段。 但是,在记录追加操作中,客户端仅需要提供数据。 GFS 会原子性的把数据追加放入文件中至少一次(即写入一个顺序的 Byte 流)并将该偏移量返回给客户端。 这类似于在 Unix 环境中以 O APPEND 模式打开文件的情况下,多个写入端没有竞争的并发写入内容。

记录追加被我们的分布式应用程序大量使用,许多客户端在不同机器上的同时向同一文件追加内容。 如果客户端使用传统写入操作,则它们将需要其他复杂且昂贵的同步操作,例如通过分布式锁管理器。 在我们的工作负载中,此类文件通常充当多生产者/单消费者队列,或来为许多不同客户端合并结果。

记录追加是一种数据变更操作,它也遵循 3.1 节描述的控制流程,仅仅是在主副本端新增了一些额外的逻辑。 客户端将数据推送到文件最后一个文件块的所有副本之后,将其请求发送到主副本。 主数据库检查是否将记录追加到当前文件块上是否会导致该文件块超过最大大小(64 MB)。 如果会超过它将填充数据块到最大大小,告诉剩余的存储节点执行相同的操作,然后回复客户端以指示应在下一个文件块上重试该操作。 (记录追加的大小被限制为最大文件块大小的四分之一,这样一来最坏情况的也能使碎片保持在可接受的水平。) 如果记录在最大大小范围内(通常是这样),则主副本将数据追加到其文件中,告诉其他副本将数据写入其具有确切偏移量的位置,最后将成功回复给客户端。

如果记录追加操作在任何副本上均执行失败,则客户端将重试该操作。 重新进行记录追加的结果是,同一个文件块的不同副本可能包含不同的数据,存在部分或者全部都是重复的数据。 GFS 不保证所有副本在字节级别上都是相同的。 它仅保证数据作为整体的原子单位至少被写入一次。 从简单的观察很容易得出这种特性,即为了报告操作成功,必须在某个文件块的所有副本上以相同的偏移量写入数据。 这之后,所有副本的长度至少与记录的长度一样长,因此,即使以后有其他副本成为主副本,任何将来的记录也将被分配更高的偏移量或者写入其他文件块。 就我们的一致性保障模型而言,成功完成记录追加操作的文件区域写入的数据是已定义的(因此是一致的),而介于写入操作中间的文件区域则是不一致的(因此是未定义的)。 正如我们在 2.7.2节中讨论的那样,我们的应用程序可以处理不一致的区域。

3.4 快照

快照操作几乎可以瞬间复制文件或目录树(源),同时最大程度地减少正在进行的突变的中断。 我们的用户使用它来快速创建大型数据集的分支副本(通常是递归创建这些副本的副本),或者用于改动测试使得可以轻松的提交或者回滚原来的状态。

像 AFS [5] 一样,我们使用标准的写时复制技术来实现快照。 当主节点接受到快照请求时,它首先要撤销掉和此快照相关的文件块上的未完成的租约。 这确保了对这些文件块的任何后续写入操作都将需要与主节点机进行交互以找到租约持有者。 这也就给了主节点创建文件块副本的机会。

租约被撤销或到期后,主节点将此操作记录到磁盘。 然后,主节点通过复制源文件或者目录的元数据的方式,把这条日志记录的变化保存在内存中。 新创建的快照文件指向与源文件相同的块。

快照操作之后,客户端第一次要写入文件块 C 的时候,它将向主节点发送请求以查找当前的租约持有者。 主节点注意到文件块 C 的引用计数大于 1。 主节点推迟了对客户请求的答复,而是选择了一个新的文件块句柄 C’。 然后,主节点要求具有当前文件块 C 的每个存储节点创建一个称为 C’ 的新文件块。 通过在与原始存储节点相同的节点上创建新的文件块,我们可以保证文件是通过本地而不是使用网络进行复制的。(我们磁盘的读写速度大约是我们 100 Mb 网络的三倍) 从这一点来看,请求处理与任何文件块都没有什么不同:主节点向存储副本之一授予新文件块 C’ 的租约,然后回复客户端,客户端可以正常地写入该文件块,而不知道该文件块是通过原现的文件块刚刚创建的。

4 主节点操作

主节点运行了所有的命名空间操作。 此外,主节点管理了整个系统中的文件块副本:它还会决定文件块的存储位置,创建新块以及副本,并协调各种系统范围内的活动以保持文件块的副本,平衡所有存储节点上的负载并回收未使用的存储空间。 现在我们来分别进行讨论。

4.1 命名空间的管理和锁定

主节点的许多操作都可能花费很久的时间:例如,快照操作必须撤消存储节点上此快照所覆盖的所有文件块上的租约。 我们不想因为快照这样的操作而影响其他操作产生延迟。 因此,我们允许多个操作处于活动状态,并在名称空间的区域上使用锁以确保正确的序列化。

与许多传统文件系统不同,GFS 没有针对每个目录实现能够列出目录下所有文件的数据结构。 它也不支持相同文件或目录的别名(即,Unix 术语中的硬链接或软链接)。 GFS 的命名空间在逻辑上就是将完整路径名映射到元数据的查找表。 使用前缀压缩,可以在内存中有效地表示该表。 命名空间树中的每个节点(绝对文件名或绝对目录名)都有一个关联的读写锁。

主节点的每个操作都需要一组锁。 通常情况下如果我们 主节点的许多操作都可能花费很久的时间:例如,快照操作必须撤消存储节点上此快照所覆盖的所有文件块上的租约。 我们不想因为快照这样的操作而影响其他操作产生延迟。 因此,我们允许多个操作处于活动状态,并在名称空间的区域上使用锁以确保正确的序列化。

与许多传统文件系统不同,GFS 没有针对每个目录实现能够列出目录下所有文件的数据结构。 它也不支持相同文件或目录的别名(即,Unix 术语中的硬链接或软链接)。 GFS 的命名空间在逻辑上就是将完整路径名映射到元数据的查找表。 使用前缀压缩,可以在内存中有效地表示该表。 命名空间树中的每个节点(绝对文件名或绝对目录名)都有一个关联的读写锁。

主节点的每个操作都需要一组锁。 通常情况下如果我们要操作 /d1/d2/.../dn/leaf,它需要以下目录的读取锁 /d1,/d1/d2,/d1/d2/../dn 并且在完整路径上获取写入锁/d1/d2/.../dn/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 集群通常分布在多个层次上。 它包含许多机架上的数百个存储节点。 而存储节点也会被来自相同或不同机架上的客户端访问。 不同机架上的两台计算机之间的通信可能会通过一个或多个网络交换机。 此外,进出机架的带宽可能小于机架内所有计算机的总带宽。 多级分发的方式在可伸缩型,可靠性和可用性上给了我们一个独特的挑战。

每个文件块的副本分配方式遵循下面两个原则:最大化数据可靠性和可用性,最大化网络带宽利用率。 为了实现这两点内容,仅仅是把副本分散在多台设备中是不够的,这样做只能防止硬盘故障或者设备失效并且利用每个设备上的网络带宽。 我们必须把文件分散在不同的机架之间。 这样做才能确保即使在整个机架损坏或者说下线的时候(例如在一些共享的资源比如说电源或者网络故障的情况下)仍然有些文件块的副本可以留存下来并且可用。 这还意味着在网络流量方面,尤其是针对文件块的读操作,能够有效利用多个机架的整合带宽。 另一方面,写操作必须和多个机架上的设备进行网络通信,但是这个代价是我们愿意付出的。

4.3 创建,重新复制,重新平衡

创建文件块的副本有以下三种原因:文件块的创建,重新复制和重新平衡。

当主节点创建文件块的时候会选择初始内容为空的副本存储位置。 主节点会考虑以下几个因素。 (1) 我们想把数据放置在磁盘使用率较低的存储节点上。最终这样做可以平衡不同存储节点上的使用率。 (2) 我们想把限制每个节点上创建操作的数量。虽然创建操作需要的资源比较少,但是创建操作也意味着随之会有大量的写入数据的操作,因为在数据写入的情况下会创建文件块,在我们一次插入多次读取的工作负载设计中,一旦它们完全被写入,它们通常实际上变成只读的。 (3) 如上所述,我们希望跨机架的散布文件块的副本。

一旦可用副本的数量低于用户设定的参数,主服务器就会重新复制文件块。 这种情况可能有多种原因,例如:存储节点不可用,存储节点报告文件副本损坏,磁盘由于故障而被禁用,或者用户修改了复制数量。 需要重新复制的每个文件块都基于以下几个因素进行排序。 一个是距离目标的数量还有多少。 例如,缺失两个副本的文件的优先级大于缺失一个副本。 另外,我们优先重新复制活跃文件的文件块而不是最近刚被删除的文件块(查看 4.4 节)。 最后,为了最大程度地减少故障对正在运行的应用程序的影响,我们提高了阻止客户端进度的文件块优先级。

主节点会选择优先级最高的文件,并通过指示某些存储节点从现在的有效副本中复制数据来完成"克隆"。 而放置新文件块的存储节点也要遵循类似于创建操作的需求:平衡磁盘利用率,限制活动的克隆操作数量以及为分发跨机架的副本。 为了防止克隆操作阻塞客户端的通信,主节点会限制群集和每个存储节点上活动的克隆操作数。 此外,每个存储节点通过限制其对源块服务器的读取请求来限制其在每个克隆操作上花费的带宽量。

最后,主节点会定期重新平衡副本:它检查当前副本的分布情况,并通过移动副本的方式优化磁盘空间和平衡存储负载。 在这个过程当中,主节点也会逐步的填满一个新的存储节点而不是快速的使用文件块和数据流充满存储节点,以至于过载。 新副本的分布标准与上面讨论的相似。 此外,主节点还必须选择要删除的现有副本。 通常,它倾向于删除空闲空间低于平均水平的块服务器上的磁盘,以使磁盘空间使用量相等。

4.4 垃圾回收机制

删除文件后,GFS 不会立即回收可用的物理存储。 GFS 空间回收采用惰性的策略,只在文件和文件块级的常规垃圾回收时进行。 我们发现这种方法使系统更加简单和可靠。

4.4.1 回收算法

当应用程序删除文件时,主节点将立即记录该删除,就像其他更改一样。 但是,不会立即回收资源,而是将文件重命名为包含删除时间戳记的隐藏文件。 在主节点常规扫命名空间时,如果发现这些文件日期距离当时超过了三天(此时间段可配置)那就会删除这些文件。 在此之前,仍可以使用新的特殊名称读取文件,并且可以通过将其重命名为正常的名称来取消删除操作。 当从名称空间中删除隐藏文件时,其内存元数据将被删除。 这有效地切断了其所有文件块的链接。

在文件块命名空间的扫描中也是相似的,主节点识别到孤立的文件块(即,无法从任何文件访问的文件块)并清除这些文件块的元数据。 在存储节点定期与主节点交换心跳信息中,每个存储节点会向主节点汇报其拥有的文件块子集,然后主节点会回复给子节点已经不在元数据当中存储的文件块。 存储节点就可以自由的删除这些文件块和副本了。

4.4.2 讨论

尽管分布式垃圾回收是一个棘手的问题,需要在编程语言的上下文中提供复杂的解决方案,但是在我们的案例中,这非常简单。 我们可以轻松地识别所有对文件块的引用:只有主节点在文件中存储并维护了所有文件块的对应关系。 我们还可以轻松地识别所有文件块的副本:它们是每个存储节点上指定目录下的 Linux 文件。 所有主节点不能识别的副本都是 “垃圾”。

垃圾回收相比与直接删除有以下几个优势。 首先,在大型分布式文件系统中组建故障是很常见的,采用垃圾回收的方式即简单又可靠。 文件块的创建可能在某些存储节点上成功但在另一些节点中会失败,这样主节点就无法知道这些副本是否存在。 副本删除的消息可能丢失,主节点必须记录这些错误然后在失败之后重新发送失败的命令,包括自身的和存储节点的。 垃圾回收提供了一种统一且可靠的方式来清理所有未知有用的副本。 其次,它将存储回收操作合并到了主节点的常规后台活动中,例如命名空间的常规扫描和与存储节点的握手。 因此操作会被批量的执行,开销了分散。 而且仅当主节点相对空闲时才执行此操作。 主节点可以更迅速地回复需要及时相应的客户请求。 再次,延迟删除的策略为意外的不可逆转的删除操作提供了安全保障。

根据我们的经验,这种方式主要缺点是,延迟回收会影响用户调优存储空间,特别是当存储空间比较紧缺的时候。 应用程序会重复的创建和删除临时文件,这样做会导致存储空间无法被马上使用。 如果删除的文件再次被明确删除,我们将通过加快存储回收来解决这些问题。 我们还允许用户将不同的复制和回收策略应用于命名空间的不同部分。 例如,用户可以制定某个目录树中文件的所有文件块都应存储而不复制,并且所有已删除的文件都会立即且不可撤消地从文件系统状态中删除。

4.5 过期失效的副本检测

如果存储节点发生故障并且在其关闭时丢失了数据变更操作,那副本可能会过期。 主节点维护了每个文件块的版本号,并使用这个版本号来区分最新的和过时的副本。

每当主节点授予文件块上的新租约时,它都会增加文件块的版本号并通知最新的副本。 主节点和副本都把最新的版本号记录到持久状态信息中。 这个行为发生在任何客户端被通知之前,因此可以在客户端写入数据之前完成。 如果另一个副本当前不可用,则其块版本号将不会被更改。 存储节点会在启动时检测现有的文件块和版本号并将它们发送给主节点,这样以来主节点就能检测到过期文件块。 如果主节点接收的版本号大于其记录中的版本号,则主节点会认为在授予租约时失败了,因此将更高的版本更新为最新版本。

主节点会在常规的垃圾回收中删除陈旧的副本。 在此之前,当它答复客户端对块信息的请求时,它实际上认为陈旧的副本根本不存在。 另一种保护措施是,当主节点在克隆操作中通知客户端哪个存储节点持有文件块的租约时或当它指示存储节点从另一个存储节点读取文件块时,主节点也会传输文件块的版本号。 客户端或存储节点在执行操作时会验证版本号,以便它始终在访问最新数据。

5 容错和诊断

在设计系统时,我们最大的挑战之一就是应对频繁出现的组件故障。 组件的数量和质量让这些问题出现的频率远远超过一般系统意外发生的频率:我们不能完全信任机器,也不能完全信任磁盘。 组件故障可能导致系统不可用,或者更糟的是损坏数据。 我们讨论我们如何面对这些挑战,以及当组件失效不可避免的发生时,使用自带工具诊断系统故障。

5.1 高可用性

在 GFS 群集中的数百台服务器中,某些服务器在任何给定时间都将不可用。 我们通过两种简单而有效的策略来保持整个系统的高可用性:快速恢复和复制。

5.1.1 快速恢复

主节点和存储节点的设计目标就是无论它们怎样终止都能在数秒之内启动并完成恢复。 实际上,我们不区分正常终止和异常终止。 通常都通过杀死进程来关闭服务器。 客户端和其他的服务器会感觉到系统不稳定,正在发出的请求会超时,需要重新连接到重启后的服务器,然后重新发送请求。 6.2.2 节记录了实际的启动时间。

5.1.2 文件块的副本

如前所述,每个文件块都复制在不同机架上的多个存储节点上。 用户可以为文件命名空间的不同部分指定不同的复制级别。 默认值为三。 主节点根据需要克隆现有的副本,以在存储节点脱机或通过校验和验证检测到损坏的副本时保持每个文件块的完全复制(请参阅5.2节)。 尽管复制为我们提供了很好的服务,但我们仍在探索其他形式的跨服务器冗余,例如奇偶校验或擦除代码,以满足日益增长的只读存储需求。 我们认为在这样松耦合的系统中实现这样复杂的冗余方案是非常有挑战性,但还是可以实现,因为我们的数据流大多是追加方式的写入和读取操作,极少情况下采取随即写入操作。

5.1.3 主节点的复制

通过复制主节点的方式可以满足可靠性需求。 主节点的操作日志和检查点会被复制到多台主机上。 仅在操作日志被记录到磁盘上并且分发到所有的主节点副本上时数据变更操作才会被确定是完成的。 为了简化整个系统,单个主进程会负责管理所有的数据变更操作和后台的活动,例如在内部修改集群状态的垃圾回收操作。 当主节点故障时,它几乎可以立即重新启动。 如果是服务器或者是磁盘故障,则 GFS 外部的监视服务将在其他位置使用复制的操作日志启动新的主节点进程。 而客户端通常使用别名的方式来连接集群(例如,gfs-test),而别名是 DNS 服务提供的(CNAME),可以通过改变目标地址的方式连接新的服务器。

此外,"影子"主节点会在主节点宕机时为应用程序提供只读的文件系统。 因为这些节点是"影子"而不是镜像,所以它们的日志和检查点会比主节点落后一些,通常是几分之一秒。 它们提高了没有经过数据变化的文件和不介意获得略微陈旧文件的应用程序的读取可用性。 实际上由于实际的数据是从存储节点读取的,所以应用程序不会发现陈旧的文件内容。 在这个短暂的时间窗内,过期的可能是文件的元数据,比如目录的内容或者访问控制信息。

为了随时了解情况,影子主机读取了不断增长的操作日志的副本,并且和主节点保持一致的元数据修改操作。 像主节点一样,它在启动时(且以后很少)向存储节点拉取文件块副本信息并与它们交换频繁的握手消息以监视其状态。 它仅依赖于主节点上的副本位置更新,该更新是由主节点决定创建和删除副本而导致的。

5.2 数据完整性

每个存储节点使用校验和来检测存储数据的是否损坏。 鉴于 GFS 群集通常在数百台服务器上具有数千个磁盘,因此它经常会遇到磁盘故障,从而导致数据丢失或读写路径丢失。 (原因请参见第7节。) 我们通过其他节点的文件块副本进行故障恢复,但是通过比较不同存储节点的文件副本的方式来检测故障是不切实际的。 此外,具有歧义的副本可能是合法的: 在 GFS 数据变化的语义中,特别是前文中描述的原子性的记录追加情况下,并不能保证副本完全一致。 因此,每个存储节点必须通过维护校验和的方式来独立验证其副本的数据完整性。

一个文件块会被分成 64 KB大小的数据块。 每个都有对应的32位校验和。 像其他元数据一样,校验和也保存在内存中,并通过日志记录与用户数据分开存储。

在读取操作时,存储节点会在任何数据返回给无论是客户端还是其他存储节点这样的请求者之前,会验证与当前读取范围重叠的数据块的校验和。 因此,存储节点不会将损坏的数据传播到其他设备中。 如果数据块与校验和不匹配,存储节点会将错误信息返回给请求者,并向主节点汇报此情况。 此时请求者将从其他副本中读取数据,而主节点会从其他副本克隆数据块。 在新的副本写入完成后主节点会命令存储节点删除错误的文件副本。

由于多种原因,校验和检测的方式对读取性能影响很小。 由于我们的大多数读取操作至少跨越几个数据块,因此我们仅需要读取和校验和相对少量的额外数据即可进行验证。 GFS 客户端代码通过把读取操作对齐在数据块边界上,来进一步减少了开销。 此外,无需任何 I/O 即可完成对存储节点的校验和查找和比较,并且校验和计算通常可以与 I/O 重叠。

由于校验和计算在我们的工作负载中占主导地位,因此它针对附加到文件块末尾的写入(与覆盖现有数据的写入相反)进行了严格的优化。 我们只是增量的更新最后不完整的数据块校验和,并为追加操作写满的新数据块计算校验和。 即使最后一个数据块以及损坏了并且我们也没有能检测出来,新生成的校验和也会和存储的数据出现不匹配的情况,并且在下次读取到此数据块时将照常检测到损坏。

在另一方面,如果写入覆盖了该文件块的现有范围,则我们必须读取并验证要覆盖范围的第一个和最后一个数据块,然后执行写入操作,最后计算并记录新的校验和。 如果我们在部分覆盖掉第一个和最后一个数据块之前没有对其进行验证,则新的校验和可能会隐藏未覆盖区域中存在的损坏。

在空闲的时候,存储节点但可以扫描和验证当前处于未被使用的文件块。 这使我们能够检测很少被读取的文件块中的损坏。 一旦检测到数据损坏,主节点即可创建新的未损坏文件的副本,并删除损坏的副本。 这样可以防止不活动但已损坏的文件块副本欺骗主节点,使其认为它具有足够的有效副本。

5.3 诊断工具

广泛而详细的诊断日志仅需要花费很少的成本就会在问题隔离,系统调试和性能分析方面提供了不可估量的帮助。 如果没有日志,则很难知道设备之间的瞬时,不可重复的交互内容。 GFS 会在服务器上生成诊断日志,该日志记录许多重要事件(例如,存储节点的启动和关闭)以及所有 RPC 请求和相应内容。 这些诊断日志可以被任意删除,而不会影响系统正常运行。 但是我们会在空间允许的情况下尽量存储这些日志。

RPC 日志包括在网络上发送的确切请求和响应内容,但不会记录读取或写入的文件数据内容。 通过收集和比对不同设备上的 PPC 日志中的请求和相应内容,我们可以重现交互历史来诊断问题。 日志还可以用来跟踪负载测试和性能分析。

日志记录对性能的影响很小(远小于它带来的好处),因为这些日志是按顺序和异步写入的。 最新事件也保存在内存中,可用于在线监控。

6 性能测试

在本节中,我们提供一些微观基准测试,以说明 GFS 架构和实施中固有的瓶颈,以及 Google 使用的实际集群中的一些真实数据。

6.1 小规模基准测试

我们在由1个主节点,2个主节点副本,16个存储节点和16个客户端组成的 GFS 群集上测量了性能。 请注意,此配置是为了便于测试而设置的。 典型的集群有数百个存储节点和数百个客户端。

所有设备都配有2个 1.4 GHz 的 PIII 处理器,2 GB 内存,2个 80 GB 5400 转的机械硬盘,并且有 100Mbps 全双工网络连接在 HP 2524 交换机上。 所有19台 GFS 服务器都连接到一台交换机,所有16台客户机都连接到另一台。 两个交换机通过 1 Gbps 链路连接。

6.1.1 读取性能

N 个客户端同时从文件系统中进行读取。 每个客户端会从 320 GB 的文件集中读取随机选择的 4 MB 区域。 重复执行256次,以便每个客户端最终读取 1 GB 数据。 存储节点共有 32 GB 内存,因此我们希望最多有 10% 的数据是从高速缓冲区中读取的。 我们的结果应该接近冷缓存结果。

图3(a) 显示了 N 个客户端的总读取速率及其理论极限。 当两个交换机之间的 1 Gbps 链路达到饱和时,限制峰值总计为 125 MB/s;如果客户端的 100 Mbps 网络接口达到饱和,则每个客户端的峰值为 12.5 MB/s (以适用者为准)。 当仅一个客户端正在读取时,观察到的读取速率为 10 MB/s,即每个客户端只能达到极限速度的 80%。 对于16个客户端同时读取时,总读取速率达到 94 MB/s, 约为 125 MB/s 链接极限的 75%,也就是说每个客户端的速度为 6 MB/s。 效率从 80% 下降到 75%,因为随着读取端数量的增加,多个读取端同时从同一存储节点读取数据的可能性也增加了。

6.1.2 写入性能

N个客户端同时写入N个不同的文件。 每个客户端以 1 MB 的写入顺序将 1 GB 的数据写入新文件。 总写入速率及其理论极限如图3(b) 所示。 理论极限速度应当稳定在 67 MB/s,因为我们需要将每个字节写入16台存储节点中的3台,而每个存储节点具有 12.5 MB/s 的输入带宽。

单个客户端的写入速度为 6.3 MB/s,约为理论极限性能的一半。 造成这种情况的主要原因是网络。 它与我们推送数据到文件块副本时采用的管道模式不相适应。 将数据从一个副本传播到另一个副本的延迟会降低总体写入速率。

16个客户端的总写入速率达到 35 MB/s(每个客户端 2.2 MB/s),约为理论极限性能的一半。 与读取的情况一样,随着客户端数量的增加,多个客户端并发写入同一存储节点的可能性更高。 而且,与16个读取端相比16个写入端遇到冲突的可能性更大,因为每次写入都会涉及到3个不同设备上的副本。

写入速度比我们预想的慢。 实际上,这并不是的主要问题,因为即使它可见的增加了各个客户端的延迟,但也不会显著的影响文件系统传递给大量客户端的聚合写入带宽。

6.1.3 追加性能

图3© 显示了记录追加的性能。 N 个客户端同时附加到单个文件。 实际性能受存储文件最后一个文件块的存储节点网络带宽的限制,而与客户端的数量无关。 对于一个客户端,速度从 6.0 MB/s 开始,而在16个客户端使它的速度降至 4.8 MB/s,这主要是由于不同的客户端上的网络拥塞以及网络传输速度的不同而导致的。

我们的应用程序倾向于同时生成多个此类文件。 换句话说,N 个客户端同时附加到 M 个共享文件中,其中 N 和 M 都有数十个或数百个。 因此,在我们的实验中,存储节点的网络拥塞实际上不是一个重要的问题,因为客户端可以在写入一个文件出现阻塞时转而去写入另一个文件。

图3 总吞吐量

注:顶部曲线显示了我们网络设备的理论极限。底部曲线为实测吞吐量。

它们具有显示95%置信区间的误差线,在某些情况下由于测量值的低差异而无法辨认。它们具有显示 95% 置信区间的误差线,在某些情况下由于测量值的低差异而无法辨认。

6.2 真实集群测试数据

现在,我们看一下 Google 内部正在使用的两个集群,它们可以代表其他几个集群。 超过100名工程师定期使用 A 集群进行研发。 一个典型的任务是由人类用户发起的,并且长达几个小时。 它会读取从几 MB 到几 TB 的数据,转换或分析数据,然后将结果写回群集。 B 群集主要用于生产数据的处理。 这些任务持续的时间更长,并且仅在偶尔的人工干预下就可以连续生成和处理多 TB 数据集。 在这两种情况下,单个“任务”都由许多计算机上的多个进程同时读取和写入许多文件组成。

6.2.1 数据存储

表2 两个GFS集群的特征

如表中的前五个条目所示,两个集群都有数百个存储节点,支持许多 TB 的存储,并且相当但不是完全满。 “已用空间”包括所有块副本。 几乎所有文件都被复制3次。 因此,群集分别存储 18 TB 和 52 TB 的文件数据。

这两个群集的文件数量相似,但是 B 集群的过期文件比例更高,即已删除或替换为新版本但其存储空间尚未被收回。 B 集群还具有更多的文件块,因为其文件往往更大。

6.2.2 元数据

存储节点会聚合存储数十 GB 的元数据,其中的内容通常是 64 KB 的用户数据块和校验和。 在存储节点上保存的唯一的元数据是 4.5 节中讨论的文件块版本号。

保留在主节点上的元数据要小得多,只有几十 MB,或者平均每个文件约100个字节。 这与我们的假设一致,即主节点的内存大小实际上不会限制系统的容量。 每个文件的元数据大多数是以前缀压缩形式存储的文件名。 其他元数据包括文件所有权和权限,从文件和文件块的映射以及每个文件块的当前版本。 另外,对于每个块,我们存储当前副本位置和应用计数,并以此来实现写时拷贝。

每个单独的服务器(主节点和存储节点)都只有50到100 MB 的元数据。 因此,恢复速度很快:在服务器能够在相应查询之前,只需几秒钟即可从磁盘读取此元数据。 但是,主节点会挂起一段时间(通常是30到60秒),直到它从所有存储节点获取到文件块的位置信息为止。

6.2.3 读写操作比例

表3 显示了不同时间段的读写速率。 进行这些测量时,两个群集都已经运行了大约一周。 (群集最近已重新启动,以升级到新版本的 GFS。)

自重启以来,平均写入速率小于 30 MB/s。 当我们进行这些测量时,B 集群正在处理突发的写入操作,该操作产生大约 100MB/s 的数据,由于数据需要传播到三个副本,因此产生了 300 MB/s 的网络负载。

读取速率远高于写入速率。 如我们所假设的那样,总工作量包含的读取次数多于写入次数。 两个集群都有很多重度的读取操作。 特别是 A 集群在前一周一直保持 580 MB/s 的读取速率。 它的网络配置可以支持 750 MB/s,因此可以有效地利用其资源。 B 群集可以支持 1300 MB/s 的峰值读取速率,但其应用程序仅使用了 380 MB/s。

表3 两个 GFS 集群的性能指标

6.2.4 主节点负载

表3 还表示了发送到主节点的操作速率约为每秒 200 到 500 个操作。 主节点可以轻松地跟上这个速度,因此对于这样的工作负载而言,主节点不是瓶颈。

在早期版本的 GFS 中,主节点有时是某些工作负载的瓶颈。 它花了大部分时间顺序扫描大型目录(包含成千上万个文件)以查找特定文件。 此后,我们更改了主节点的数据结构,使其能通过命名空间进行二分查找来搜索改善效率。 现在,它可以轻松地支持每秒数千个文件访问。 如有必要,我们可以通过在命名空间数据结构之前放置名称缓存并以此来进一步加快查询速度。

6.2.5 故障恢复时间

在存储节点出现故障后,某些文件块的会缺失副本的数量,必须对其进行克隆操作来达到目标的副本数量。 恢复所有的此类文件块所需的时间取决于需要恢复多少文件块。 在一次实验中,我们杀死了 B 集群的一个存储节点。 这个存储节点有大约 15000 个文件块,其中包含 600 GB 的数据。 为了限制测试对现有应用程序的影响并为集群调度决策留出余地,我们的默认参数将集群内并发的克隆操作数限制为了 91 个(40%的存储节点数量),其中每个克隆操作最多允许消耗 6.25 MB/s (50 Mbps) 带宽。 所有文件块均在 23.2 分钟内恢复,有效复制速率为 440 MB/s。

在另一次实验中,我们杀死了两个带有大约 16,000 个块和 660 GB 数据的存储节点。 这次的双重故障使 266 个文件块减少为只有一个副本。 这 266 个文件块以较高的优先级进行克隆,并在 2 分钟内全部恢复到至少含有 2 个副本,从而使群集处于可以忍受另一个存储节点故障而不会丢失数据的状态。

6.3 工作负载分析

在本节中,我们将详细介绍了两个 GFS 群集上的工作负载情况,这些情况与 6.2 节中的工作负载情况类似,但不完全相同。 X 集群用于研发,Y 集群用于生产数据处理。

6.3.1 方法和注意事项

这些测试结果仅包括客户端发出的请求,因此它们反映了我们的应用程序为整个文件系统生成的工作量。 它们中并不包含执行客户端请求时服务器间的内部请求或后台活动,例如集群内数据的转发写入或重新平衡。

I/O 操作的统计信息是从 GFS 服务器中的 RPC 请求日志中推导重建出来的。 例如,GFS 客户端代码可能将读取拆分为多个 RPC,以增加并行度,从中我们可以推断出原始读取负载。 由于我们的访问模式是高度程式化的,因此我们推测任何错误都是来自于误差。 应用程序的显式日志记录可能会提供更准确的数据,但是从逻辑上讲,重新编译并重新启动成千上万个正在运行的客户端是不可能的,而且收集在很多设备上的运行结果这也是项非常繁重的工作。

应该注意的是不要过度概括我们的工作量。 由于 Google 完全控制了 GFS 及其应用程序,因此这些应用程序应当倾向于针对 GFS 进行调整,但事实完全相反,GFS 是为这些应用程序设计的。 在通用应用程序和文件系统之间也可能存在这种相互影响,但是在我们的案例中,这种影响可能更加明显。

6.3.2 存储节点工作负载

表4 不同大小文件和不同操作的负载占比(%)

注: 对于读取操作而言,表中的大小是实际读取和传输的数据量,而不是请求的数据量。

表4 显示了不同大小之间操作分布情况。 读取操作的结果是明显的双峰分布。 小型读取操作(小于 64 KB)来自于搜索密集型的客户端,这些客户端在大文件中查询少量数据。 大型读取操作(超过 512 KB)来自于对整个文件的长时间读取。

在 Y 集群上有很多的读取操作甚至没有返回任何数据。 我们的应用程序,尤其是生产系统中的应用程序,经常使用文件作为生产者-消费者队列。 生产者将同时向文件发起追加操作,而使用者则会读取文件的末尾。 有时,当消费者读取的位置超过生产者写入的位置时就不会返回任何数据。 X 集群则不会有这样的情况,因为它通常用于短期的数据分析任务,而不是长期运行的分布式应用程序。

写入操作的结果也是明显的双峰分布。 大型的写入操作(超过 256 KB)通常是写入端的大量缓冲造成的。 写入端会缓存一部分数据,更多情况下是记录存档点或者是进行同步操作,又或者是为小型的写入操作(小于 64 KB)分配账户。

注: 此处翻译可能不准确,原文是: Writers that buffer less data, checkpoint or synchronize more often, or simply generate less data account for the smaller writes (under 64 KB).

至于记录追加操作,Y 集群的大型追加操作所占的百分比远远大于 X 集群,这是因为 Y 集群是我们的生产集群,所以做了更贴合 GFS 的相关调优。

表5 不同大小文件和不同操作的数据传输负载占比(%)

注: 对于读取操作而言,表中的大小是实际读取和传输的数据量,而不是请求的数据量。 如果读取尝试读取超出文件末尾的数据,则两者可能会有所不同,这在设计上在我们的工作负载中并不罕见。

表5 列出了各种大小的操作中传输的数据总量。 对于所有类型的操作,较大的操作(超过 256 KB)通常占用传输的大多数字节。 因为检索操作相关的负载,所以小型读取操作(小于 64 KB)会传输一部分重要的数据。

6.3.3 追加和写入的对比

在我们的生产环境中大量使用了记录追加操作。 在 X 集群中,写入操作和追加操作的字节传输比例是 108:1,操作计数的比例是 8:1。 对于生产环境的 Y 集群来说,这样的比例是 3.7:1 和 2.5:1。 而且,这些比例表明,对于这两个集群来说,记录追加操作往往大于写入操作。 但是对于 X 集群来说,在测量期间追加操作的使用率特别的低,因此结果可能会被一两个选用特定大小缓冲区的应用程序影响。

不出所料,我们的数据变更的工作负载主要是附加而不是覆盖。 我们测量了主副本上覆盖的数据量。 这近似于客户端故意覆盖先前写入的数据而不是附加新数据的情况。 对于 X 群集,重写修改了不足 0.0001% 的数据并且数据变更操作占比不足 0.0003% 。 对于 Y 群集,比例均为0.05%。 尽管比较小,但仍然比我们预期的要高。 事实证明,这些覆盖操作大多数是由于错误或超时而导致的客户端重试。 它们本身不是工作量的一部分,而是由重试机制引发的后果。

6.3.4 主节点负载对比

表6 主节点请求类型占比(%)

表6 显示了主节点接受到的请求类型占比。 大多数的请求都是在查询文件块的位置信息以及为数据变更请求租约持有者信息。

X 集群和 Y 集群上的删除请求数有明显的不同,因为 Y 集群中存储的是数据都是生产数据,这些数据会被定期的重建并且替换成最新的版本。 某些差异进一步隐藏在打开文件的请求中,因为旧版本的文件可能会通过从头写入的操作而被隐式删除(类似 UNIX 的 open 函数中的 “w” 模式)。

FindMatchingFiles 是一个模式匹配请求,它支持 “ls” 和类似的文件系统操作。 与其他请求主节点的操作不同,这样的操作可能会处理很大一部分的命名空间数据,因此可能花费的性能和时间比较多。 在 Y 集群中经常见到这样的操作,因为自动的数据处理任务倾向于检查文件系统的各个组成部分以了解全局应用程序的状态。 相比之下,X 集群的应用程序则受到了更明确的控制,通常事先知道所需文件的名称。

7 经验

在构建和部署 GFS 的过程中,我们遇到了许多问题,一些是运营方面的,有些是技术方面的。

最初,GFS 被我们认为是生产环境的后端文件系统。 随着时间推移,它的使用涉及了研究和开发任务。 它最初几乎不支持权限和配额之类的东西,但现在已经包含了这些内容。 尽管生产系统受到严格的纪律和控制,但用户有时却没有。 需要更多的基础架构来防止用户之间相互干扰。

我们最大的困难是与磁盘和一些 Linux 相关的问题。 我们的许多磁盘都向 Linux 驱动程序声明它们支持多种 IDE 协议版本,但实际上仅对较新的版本做出了可靠的响应。 由于协议版本非常相似,因此这些驱动大多数都可以工作,但是偶尔的不兼容会导致驱动和内核对于驱动状态有不一样的判断。 这会使内核中的问题静默的破坏数据。 这个问题促使我们使用校验和来检测数据损坏,而在同时我们也修改了内核以处理这些协议不匹配。

在早期,由于 fsync() 的系统开销,我们在 Linux 2.2 内核上遇到了一些问题。 这种系统开销与文件的大小成正比而不是与修改部分的大小成正比。 对于我们的大型操作日志而言,这是一个问题,尤其是在实施检查点之前。 在一段时间中我们使用同步写入的方式解决此问题,但最后还是迁移到了 Linux 2.4。

另一个关于 Linux 的问题是单个的读取-写入锁,当程序从磁盘中读取数据时(读取锁)和在 mmap() 修改地址空间时在地址空间中的任何线程都必须持有锁。 在轻负载下,我们检查到系统中出现了瞬时超时,并努力定位资源瓶颈或是偶发的硬件故障。 最终,我们发现,当磁盘线程将数据映射至分页时,单个锁会阻止主网络线程将新数据映射到内存中。 由于我们主要带宽限制是在网络而不是内存上,故我们通过使用 pread() 替换 mmap() 的方式来解决此问题,但此方法额外消耗了一次拷贝操作。

尽管偶尔出现问题,Linux 代码的可用性还是一次又一次地帮助我们探索和理解系统行为。 在适当的时候,我们会把内核相关的改进并与开源社区共享。

8 相关的工作

像 AFS [5] 等其它大型分布式文件系统一样,GFS 提供了独立于物理位置的命名空间,这可以使数据透明的进行移动来实现负载均衡和容错。 但与 AFS 不同,GFS 以类似于 xFS [1] 和 Swift [3] 的方式在服务器之间进行数据分发,以提供高性能并增强容错能力。

由于磁盘相对便宜,并且复制比复杂的 RAID [9] 方法更简单,因为 GFS 当前仅使用复制的方式来实现冗余,所以会比 xFS 或 Swift 消耗更多的存储资源。

与 AFS,xFS,Frangipani [12] 和 Intermezzo [6] 等系统相比,GFS 在文件系统接口之下不提供任何缓存功能。 我们的目标工作负载在单个应用程序的运行中几乎没有重用,因为在实际情况中要么是流式处理大量的数据集,要么是随机搜索每次只读取少量的数据。

例如 Frangipani, xFS, Minnesota’s GFS [11] and GPFS [10] 等分布式文件系统选择移除中心节点,并依赖分布式算法的方式来保证一致性和管理集群。 我们选择集中化的方式来简化设计,提高可靠性,获取灵活性。 特别是中心的主节点可以让复杂的文件块分发和复制策略变得容易很多,因为主节点已经有了大多数的相关信息兵器能够控制这些内容。 我们通过保持少量的主机状态数据并在其它计算机上完全复制这些数据来解决容错问题。 而影子主机机制则提供了可伸缩性和高可用性(在读取操作上)。 主节点的状态更新是通过追加写入持久化存储的预写日志实现的。 因此,我们可以像 Harp [7] 那样使用主节点复制的方案来提供更强的一致性的高可用性。

我们在处理为大量用户提供统一的性能的问题时选择了类似于 Lustre [8] 的处理方案。 但是,我们通过专注于应用程序的需求而不是构建 POSIX 兼容的文件系统的方式大大的简化了问题。 此外,GFS 推测有大量不可靠的硬件,因此容错对于我们的设计至关重要。

GFS 更像是 NASD 架构 [4]。 尽管 NASD 架构是基于网络连接的磁盘驱动器,但是 GFS 和 NASD 原型一样都是使用了则是一般市面上的计算机作为存储节点。 与 NASD 工作方式不同,我们的存储节点使用的是延迟分配的固定大小的文件块而不是可变长度的对象。 此外,GFS 实现了生产环境中所需的某些功能,例如重新平衡,复制和恢复。

与 Minnesota’s GFS 和 NASD 不同,我们去改变存储设备的型号。 我们专注于满足使用现有商品级别的硬件来构建复杂分布式系统以满足日常数据处理需求。

由原子性的记录追加操作实现的生产者-消费者队列解决了与 River [2] 类似的分布式队列问题。 与 River 使用基于内存的分布式队列加上严格的数据流控制的实现方式不同,GFS 采用了可以被多个生产者并发写入的持久化文件的方案。 River 模型支持了 m 对 n 的分布式队列,但缺乏持久存储附带的容错能力,而 GFS 仅有效支持 m 对 1 队列。 多个消费者可以同时读取一个文件,但是它们必须协调的分配传入的负载。

9. 结论

谷歌文件系统展示了支持商品级硬件上的大规模数据处理工作负载所必需的品质。 虽然一些设计决策是针对独特设置指定的,但许多决策可能适用于规模和成本意识相似的数据处理任务。

首先,我们根据我们当前和预期的工作负载和技术环境重新检查传统文件系统的假设。 我们的观察结果导致了设计方面的根本区别。 我们将组件故障视为正常现象,而不是例外情况,针对大型文件进行优化,因为这些文件通常会被追加(也许同时进行),然后再读取(通常是有序进行),并通过扩展和放松标准文件系统接口的方式改善整个系统。

我们的系统通过持续监控,复制关键数据以及快速自动恢复来提供容错能力。 文件块复制的机制使我们能够容忍存储节点故障。 这些故障的发生率激发了一种新颖的在线修复机制,该机制定期透明地修复损坏的文件块并尽快补充复制集。 此外,我们使用校验和来检测磁盘或 IDE 子系统级别的数据损坏,鉴于系统中的磁盘数量,这种情况变得非常普遍。

我们的设计为执行各种任务的许多并发读写端提供了高额的总吞吐量。 我们通过将主节点的文件系统控制与直接在存储节点和客户端之间传递的数据传输的内容进行分离的方式实现了这样的效果。 通过大型文件块和租约的方式可以将主节点涉及到的常见操作减至最小,这样的方式给了主副本进行数据变更的权限。 这使得集群中存在一个简单,集中不会成为瓶颈的主节点成为可能。 我们相信,在网络方面的改进将解除当前单个客户端上的写入吞吐量的限制。

GFS 已成功满足了我们的存储需求,并已在 Google 内部广泛将它作为研究,开发以及生产数据处理的存储平台。 它是使我们持续创新和解决整个 Web 范围内的难题的一个重要工具。

致谢

1
2
3
4
5
6
7
8
9
10
11
12
We wish to thankthe 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 desJardins contributed to the early design. Fay Chang worked
on comparison of replicas across chunkservers. Guy Edjlali worked on storage quota. Markus Gutschke worked
on a testing frameworkand 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 networkfile 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 diskstriping 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, ErikRiedel, 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] FrankSchmuckand Roger Haskin. GPFS: A shared-diskfile 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.


The Google File System 中文翻译版
https://wangqian0306.github.io/2021/the_google_file_system/
作者
WangQian
发布于
2021年1月7日
许可协议