Google File System 谷歌文件系统 论文[译]

The Google File System Sanjay Ghemawat, Howard Gobioff, and Shun Tak Leung Google

我们设计并实现了 Google 文件系统,这是一种可扩展的分布式文件系统,适用于大型分布式数据密集型应用程序。 它在廉价的商品硬件上运行时提供容错,并为大量客户端提供高聚合性能。虽然与以前的分布式文件系统有许多相同的目标,但我们的设计是由对我们的应用程序工作负载和技术环境的观察驱动的, 当前的和预期的,这反映了与一些早期文件系统假设的明显背离。 这导致我们重新审视传统选择并探索截然不同的设计点。文件系统成功地满足了我们的存储需求。它在谷歌内部广泛部署,作为我们服务和研发所用数据的生成和处理的存储平台 需要大量数据集的工作。 迄今为止最大的集群在一千多台机器上的数千个磁盘上提供数百 TB 的存储,并由数百个客户端同时访问。在本文中,我们介绍了旨在支持分布式应用程序的文件系统接口扩展,讨论了我们设计的许多方面, 并报告来自微基准和现实世界使用的测量结果

我们设计并实施了 Google 文件系统 (GFS),以满足 Google 数据处理需求快速增长的需求。 GFS 与以前的分布式文件系统有许多相同的目标,例如性能、可伸缩性、可靠性和可用性。 然而,它的设计是由我们对当前和预期的应用程序工作负载和技术环境的关键观察所驱动的,这反映了与一些早期文件系统设计假设的明显背离。 我们重新审视了传统选择,并探索了设计空间中截然不同的点。首先,组件故障是常态而非例外。 文件系统由数百甚至数千台由廉价商品部件构建的存储机器组成,并由相当数量的客户端机器访问。 组件的数量和质量实际上保证了某些组件在任何给定时间都无法正常工作,而某些组件将无法从当前故障中恢复。 我们已经看到由应用程序错误、操作系统错误、人为错误以及磁盘、内存、连接器、网络和电源故障引起的问题。 因此,持续监控、错误检测、容错和自动恢复必须是系统不可或缺的一部分。其次,按照传统标准,文件是巨大的。 多 GB 的文件很常见。 每个文件通常包含许多应用程序对象,例如 Web 文档。 当我们经常处理包含数十亿个对象的许多 TB 的快速增长的数据集时,即使文件系统可以支持,管理数十亿个大约 KB 大小的文件也很笨拙。 因此,必须重新考虑设计假设和参数,例如 I/O 操作和块大小。第三,大多数文件是通过附加新数据而不是覆盖现有数据来改变的。 文件中的随机写入实际上是不存在的。 一旦写入,文件只能被读取,而且通常只能按顺序读取。 各种数据都具有这些特征。 有些可能构成数据分析程序扫描的大型存储库。 有些可能是由运行的应用程序不断生成的数据流。 有些可能是档案数据。 有些可能是在一台机器上产生并在另一台机器上同时或稍后处理的中间结果。 鉴于这种对大文件的访问模式,附加成为性能优化和原子性保证的重点,而在客户端缓存数据块失去了吸引力。第四,协同设计应用程序和文件系统API通过增加我们的灵活性使整个系统受益

例如,我们放宽了 GFS 的一致性模型,以在不给应用程序带来沉重负担的情况下极大地简化文件系统。 我们还引入了解剖追加操作,以便多个客户端可以同时追加到一个文件,而无需在它们之间进行额外的同步。 这些将在本文后面进行更详细的讨论。目前部署了多个 GFS 集群用于不同的目的。 最大的一个有超过 1000 个存储节点,超过 300 TB 的磁盘存储,并且被不同机器上的数百个客户端连续大量访问

假设 在为我们的需要设计文件系统时,我们一直以既提供挑战又提供机会的假设为指导。 我们之前提到了一些关键观察结果,现在更详细地阐述了我们的假设。 • 该系统由许多经常出现故障的廉价商品组件构建而成。 它必须不断地自我监控,定期检测、容忍组件故障并从中迅速恢复。• 系统存储适量的大文件。 我们预计有几百万个文件,每个文件的大小通常为 100 MB 或更大。 多 GB 的文件是常见的情况,应该有效地管理。 小文件必须支持,但我们不需要为它们优化。• 工作负载主要包括两种读取:大流式读取和小随机读取。 在大型流式读取中,单个操作通常会读取数百 KB,更常见的是 1 MB 或更多。来自同一客户端的连续操作通常会读取文件的连续区域。 一个小的随机读取通常在某个任意偏移处读取几个 KB。 注重性能的应用程序通常对它们的小读取进行批处理和排序,以在文件中稳定前进,而不是来回移动。• 工作负载也有许多将数据附加到文件的大的、顺序的写入。 典型的操作大小与读取的操作大小相似。 一旦写入,文件就很少再被修改。 支持文件中任意位置的小型写入,但不一定要高效。• 系统必须为同时附加到同一文件的多个客户端高效地实现定义明确的语义。 我们的文件通常用作生产者消费者队列或用于多路合并。 数百个生产者,每台机器运行一个,将同时追加到一个文件。 具有最小同步开销的原子性是必不可少的。 文件可能会稍后读取,或者消费者可能会同时读取文件。• 高持续带宽比低延迟更重要。 我们的大多数目标应用程序都非常重视以高速率批量处理数据,而很少有对单个读取或写入的响应时间有严格要求的

接口 GFS 提供了一个熟悉的文件系统接口,尽管它没有实现标准的 API,例如 POSIX。 文件在目录中分层组织并由路径名标识。 我们支持常用的创建、删除、打开、关闭、读取和写入文件的操作。此外,GFS 具有快照和记录追加操作。 快照以低成本创建文件或目录树的副本。 Record append 允许多个客户端同时将数据附加到同一个文件,同时保证每个客户端附加的原子性。 它对于实现多路合并结果和生产者消费者队列很有用,许多客户端可以同时附加到这些队列而无需额外锁定。 我们发现这些类型的文件在构建大型分布式应用程序时非常有用。 快照和记录追加分别在第 3.4 节和第 3.3 节中进一步讨论。 2.3 体系结构一个 GFS 集群由一个主服务器和多个块服务器组成,并由多个客户端访问,如图 1 所示。每个客户端通常都是运行用户级的商用 Linux 机器 服务器进程。 在同一台机器上同时运行 chunkserver 和 client 是很容易的,只要机器资源允许,并且可以接受运行可能不稳定的应用程序代码导致的较低可靠性。文件被分成固定大小的块。 每个块由主在创建块时分配的不可变且全局唯一的 64 位块句柄标识。块服务器将块作为 Linux 文件存储在本地磁盘上,并读取或写入由块句柄和字节范围指定的块数据。 为了可靠性,每个块都被复制到多个块服务器上。 默认情况下,我们存储三个副本,尽管用户可以为文件命名空间的不同区域指定不同的复制级别。主服务器维护所有文件系统元数据。 这包括名称空间、访问控制信息、从文件到块的映射以及块的当前位置。它还控制系统范围的活动,例如块租用管理、孤立块的垃圾收集和块服务器之间的块迁移。 master 周期性地与 HeartBeat 消息中的每个 chunkservers 通信,给它指令并收集它的状态。链接到每个应用程序的 GFS 客户端代码实现文件系统 API 并与 master 和 chunkservers 通信以代表应用程序读取或写入数据。 客户端与 master 交互以进行元数据操作,但所有数据承载通信直接进入块服务器。 我们不提供 POSIX API,因此不需要连接到 Linux vnode 层。客户端和 chunkserver 都不会缓存文件数据。客户端缓存几乎没有什么好处,因为大多数应用程序流经大量文件或工作集太大而无法缓存。 没有它们通过消除缓存一致性问题简化了客户端和整个系统。(然而,客户端确实缓存元数据。)块服务器不需要缓存文件数据,因为块存储为本地文件,因此 Linux 的缓冲区缓存已经将经常访问的数据保存在内存中

Single Master 拥有一个 master 极大地简化了我们的设计,并使 master 能够使用全局知识做出复杂的块放置和复制决策。 但是,我们必须尽量减少它对读写的参与,以免成为瓶颈。 Clients 永远不会通过 master 读写文件数据。 取而代之的是,客户端询问主控器它应该联系哪些块服务器。 它在有限的时间内缓存此信息,并直接与块服务器交互以进行许多后续操作。让我们参考图 1 解释简单读取的交互。首先,使用固定的块大小,客户端翻译由指定的文件名和字节偏移量 application 到文件中的 chunkindex 中。 然后,它向 master 发送一个包含文件名和块索引的请求。 master 回复相应的 chunkhandle 和 replicas 的位置。 客户端使用文件名和块索引作为键来缓存此信息。然后,客户端将请求发送到其中一个副本,很可能是最近的副本。 该请求指定块句柄和该块内的字节范围。 在缓存信息过期或文件重新打开之前,对同一块的进一步读取不再需要客户端与主机交互。事实上,客户端通常会在同一请求中请求多个块,主机也可以包含紧跟在请求之后的块的信息。 这个额外的信息在几乎没有额外成本的情况下回避了几个未来的客户与主交互。2.5 块大小Chunksize 是关键设计参数之一。 我们选择了 64 MB,这比典型的文件系统块大小要大得多。 每个 chunkreplica 都作为 plainLinux 文件存储在 chunkserver 上,仅在需要时进行扩展。懒惰的空间分配避免了由于内部碎片造成的空间浪费,这可能是反对如此大的 chunksize 的最大反对意见。大的 chunksize 提供了几个重要的优势。首先,它减少了客户端 ' 需要与 master 交互,因为在同一个块上读取和写入只需要向 master 发出一个初始请求以获取块位置信息。 这种减少对于我们的工作负载来说尤其重要,因为应用程序主要是顺序读取和写入大文件。 即使对于小的随机读取,客户端也可以轻松缓存多 TB 工作集的所有块位置信息。 其次,由于在大块上,客户端更有可能在给定块上执行许多操作,它可以通过在延长的时间段内保持与块服务器的持久 TCP 连接来减少网络开销。 第三,它减少了存储在 master 上的元数据的大小。 这允许我们将元数据保留在内存中,这反过来又带来了我们将在第 2.6.1 节中讨论的其他优势。另一方面,大块大小,即使使用惰性空间分配,也有其缺点。 一个小文件由少量块组成,也许只有一个。 如果许多客户端访问同一个文件,存储这些块的块服务器可能会成为热点。 实际上,热点并不是一个主要问题,因为我们的应用程序大多顺序读取大型多块文件。但是,当批处理队列系统首次使用 GFS 时,热点确实出现了:将可执行文件作为单块文件写入 GFS,然后启动 同时在数百台机器上。 存储此可执行文件的几个块服务器因数百个同时请求而过载。 我们通过以更高的复制因子存储此类可执行文件并使批处理队列系统错开应用程序启动时间来解决此问题。 一个潜在的长期解决方案是允许客户端在这种情况下从其他客户端读取数据

2.6 元数据 master 存储三种主要类型的元数据:文件和块命名空间,从文件到块的映射,以及每个块的副本的位置。 所有元数据都保存在主人的记忆中。 前两种类型(命名空间和文件到块映射)也通过将变更记录到存储在主服务器本地磁盘上并复制到远程机器上的操作日志中来保持持久性。 使用日志允许我们简单、可靠地更新主状态,并且在主崩溃的情况下不会有不一致的风险。 master 不会持久存储 chunklocation 信息。 相反,它会在 master 启动时以及每当 chunkserver 加入集群时向每个 chunkserver 询问它的 chunk。2.6.1 内存数据结构由于元数据存储在内存中,master 操作很快。 此外,master 在后台定期扫描其整个状态既简单又高效。这种定期扫描用于实现块垃圾收集、块服务器故障时的重新复制以及块迁移以平衡块服务器之间的负载和磁盘空间使用。 4.3 和 4.4 节将进一步讨论这些活动。这种仅内存方法的一个潜在问题是,块的数量以及整个系统的容量受到 master 拥有的内存量的限制。 这在实践中并不是一个严重的限制。 master 为每个 64 MBchunk 维护少于 64 字节的元数据。 大多数块是满的,因为大多数文件包含许多块,只有最后一个块可能被部分填充。 类似地,文件命名空间数据通常需要每个文件少于 64 个字节,因为它使用前缀压缩紧凑地存储文件名。如果需要支持更大的文件系统,向主服务器添加额外内存的成本对于简单性、可靠性来说是一个很小的代价 、性能和灵活性,我们通过将元数据存储在内存中获得收益。2.6.2 块位置主服务器不保留关于哪个块服务器具有给定块的副本的持久记录。 它只是在启动时轮询服务器以获取该信息。 master 之后可以使自己保持最新,因为它控制所有块的放置并通过定期的 HeartBeat 消息监视 chunkserver 状态。我们最初尝试将 chunk 位置信息持久保存在 master 中,但我们认为在启动时从 chunkservers 请求数据要简单得多 ,然后定期。 这消除了在 chunkservers 加入和离开集群、更改名称、失败、重新启动等时保持 master 和 chunkservers 同步的问题。 在一个有数百台服务器的集群中,这些事件发生得太频繁了。理解这个设计决策的另一种方法是认识到块服务器对它在自己的磁盘上有或没有什么块有最终决定权。 在 master 上维护此信息的一致视图是没有意义的,因为块服务器上的错误可能导致块自发消失(例如,磁盘可能损坏并被禁用)或者操作员可能重命名块服务器。 2.6.3 操作日志操作 日志包含关键元数据更改的历史记录。 它是 GFS 的核心。 它不仅是元数据的唯一持久记录,而且还是定义并发操作顺序的逻辑时间线。 文件和块,以及它们的版本(参见第 4.5 节),都是由它们创建的逻辑时间唯一和永久标识的。由于操作日志很关键,我们必须可靠地存储它,并且在元数据发生变化之前不要让更改对客户端可见 变得持久。 否则,我们实际上会丢失整个文件系统或最近的客户端操作,即使这些块本身仍然存在。 因此,我们将它复制到多台远程机器上,只有在本地和远程将相应的日志记录刷新到磁盘后才响应客户端操作。 master 在刷新之前将多个日志记录一起批处理,从而减少刷新和复制对整个系统吞吐量的影响 master 通过重放操作日志来恢复其文件系统状态。 为了最小化启动时间,我们必须保持日志小。 每当日志增长超过一定大小时,master 就会检查它的状态,以便它可以通过从本地磁盘加载最新的检查点并在之后仅重放有限数量的日志记录来恢复。 检查点是一种紧凑的类似 B 树的形式,可以直接映射到内存中并用于名称空间查找而无需额外的解析。 这进一步加快了恢复速度并提高了可用性。因为构建检查点可能需要一段时间,所以 master 的内部状态的结构使得可以在不延迟传入突变的情况下创建新的检查点。 master 切换到一个新的日志文件,并在一个单独的线程中创建新的检查点。 新检查点包括切换前的所有突变。 它可以在一分钟左右的时间内创建拥有几百万个文件的光彩。 完成后,它会写入本地和远程磁盘。恢复只需要最新的完整检查点和后续日志文件。 较旧的检查点和日志文件可以自由删除,尽管我们保留了一些以防止灾难发生。 检查点期间的失败不会影响正确性,因为恢复代码会检测并跳过不完整的检查点

2.7 一致性模型 GFS 有一个宽松的一致性模型,它很好地支持我们的高度分布式应用程序,但实现起来仍然相对简单和高效。 我们现在讨论 GFS 的保证及其对应用程序的意义。 我们还强调了 GFS 如何维护这些保证,但将细节留给本文的其他部分。2.7.1 GFS 的保证文件命名空间突变(例如,文件创建)是原子的。它们由 master 专门处理:命名空间锁定保证原子性和正确性(第 4.1 节) );master 的操作日志定义了这些操作的全局总顺序(第 2.6.3 节)。数据突变后文件区域的状态取决于突变的类型,成功或失败,以及是否存在并发突变。 表 1 总结了结果。 如果所有客户端将始终看到相同的数据,无论它们从哪个副本读取,文件区域都是一致的。 如果文件数据突变一致,则在文件数据突变后定义一个区域,并且客户端将看到突变写入的完整内容。 当一个突变在没有并发写入者干扰的情况下成功时,受影响的区域被定义(并且暗示一致):所有客户端将始终看到突变写入的内容。 并发成功的突变使该区域未定义但保持一致:所有客户端都看到相同的数据,但它可能不会反映任何一个突变所写的内容。 通常,它由来自多个突变的混合片段组成。 失败的突变会使区域不一致(因此也是未定义的):不同的客户端可能在不同的时间看到不同的数据。 我们在下面描述我们的应用程序如何区分定义区域和未定义区域。 应用程序不需要进一步区分不同类型的未定义区域。数据突变可能是写入或记录追加。 写入导致数据写入应用程序指定的文件偏移量。 记录追加导致数据(“记录”)被原子地追加至少一次,即使存在并发突变,但在 GFS 选择的偏移量处(第 3.3 节)。 (相比之下,“常规”附加只是在客户端认为是文件当前结尾的偏移量处写入。)偏移量返回给客户端并标记包含记录的已定义区域的开始。此外,GFS 可能会在其间插入填充或重复记录。 它们占据的区域被认为是不一致的,并且通常与用户数据量相比相形见绌。在一系列成功的变异之后,变异的文件区域保证被定义并包含最后一次变异写入的数据。 GFS 通过以下方式实现这一点:(a)在所有副本上以相同的顺序对一个块应用突变(第 3.1 节),以及(b)使用块版本号来检测任何已经过时的副本,因为它在其块服务器关闭时错过了突变(第 4.5 节) ). 陈旧副本永远不会参与突变或提供给向主服务器询问块位置的客户端。 它们是尽早收集的垃圾。由于客户端缓存块位置,它们可能会在刷新该信息之前从陈旧的副本中读取。 此窗口受缓存条目的超时和文件的下一次打开限制,从缓存中清除该文件的所有块信息。 此外,由于我们的大多数文件都是附加的,陈旧的副本通常会返回块的过早结束而不是过时的数据。 当读取器重试并联系主控器时,它会立即获得当前的块位置。在成功突变很久之后,组件故障当然仍然会损坏或破坏数据。 GFS 通过 master 和 allchunkservers 之间的定期握手来识别失败的 chunkservers,并通过校验和检测数据损坏(第 5.2 节)。 一旦出现问题,就会尽快从有效副本中恢复数据(第 4.3 节)。 只有在 GFScan 做出反应之前(通常在几分钟内)所有副本都丢失,一个块才会不可逆转地丢失。 即使在这种情况下,它也变得不可用,而不是损坏:应用程序收到清晰的错误而不是损坏的数据

2.7.2 对应用程序的影响 GFS 应用程序可以适应松散的一致性模型和其他目的已经需要的一些简单技术:依靠追加而不是覆盖、检查点和编写自我验证、自我识别的记录。实际上我们所有的应用程序都通过追加而不是修改文件 比覆盖。 在一个典型的用途中,编写器从头到尾生成一个文件。 它在写入所有数据后自动将文件重命名为永久名称,或者定期检查已成功写入多少。 检查点还可能包括应用程序级校验和。 读取器仅验证和处理直到最后一个检查点的文件区域,已知该区域处于已定义状态。 抛开一致性和并发性问题不谈,这种方法对我们很有帮助。 与随机写入相比,追加对应用程序故障的效率和弹性要高得多。 检查点允许写入者以增量方式重新启动,并防止读取者处理从应用程序的角度来看仍然不完整的成功写入的文件数据。在其他典型用途中,许多写入者并发附加到文件以获得合并结果或作为生产者 - 消费者队列。 Record append 的 append-at-least-once 语义保留了每个 writer 的输出。 读者处理偶尔的填充和重复如下。 作者准备的每条记录都包含校验和等额外信息,以便验证其有效性。 读者可以使用校验和识别和丢弃额外的填充和记录片段。 如果它不能容忍偶尔的重复(例如,如果它们会触发非幂等操作),它可以使用记录中的唯一标识符将它们过滤掉,无论如何通常需要这些标识符来命名相应的应用程序实体,例如 Web 文档。 这些用于记录 I/O 的功能(重复删除除外)在我们的应用程序共享的库代码中,适用于 Google 的其他文件接口实现。 这样,相同序列的记录,加上罕见的重复,总是被传送到记录 reader.3。 系统交互 我们设计系统是为了尽量减少船长对所有操作的参与。 在此背景下,我们现在描述客户端、主服务器和块服务器如何交互以实现数据突变、原子记录追加和快照。 3.1 租约和突变顺序突变是更改块的内容或元数据的操作,例如写入或追加 手术。 每个突变都在块的所有副本上执行。我们使用租约来保持跨副本的一致突变顺序。 主人向其中一个副本授予块租约,我们称之为主副本。 primary 为块的所有突变选择一个序列顺序。 应用突变时,所有副本都遵循此顺序。 因此,全局突变顺序首先由 master 选择的租约授予顺序定义,并在租约内由 primary 分配的序列号定义。租约机制旨在最大限度地减少 master 的管理开销。 租用的初始超时为 60 秒。 但是,只要块发生变化,主块就可以无限期地请求并通常从主块接收扩展。 这些扩展请求和授权是在 master 和所有 chunkservers 之间定期交换的 HeartBeat 消息上搭载的。master 有时可能会在它到期之前尝试撤销租约(例如,当 master 想要禁用正在重命名的文件上的突变时)。 即使 master 失去与 primary 的通信,它也可以在旧租约到期后安全地向另一个副本授予新租约。在图 2 中,我们通过这些编号的步骤遵循写入的控制流程来说明此过程。 客户端向主服务器询问哪个块服务器拥有该块的当前租约以及其他副本的位置。 如果没有人有租约,mastergrants 一个给它选择的副本(未显示)

  1. master 回复主副本的身份和其他(次要)副本的位置。 客户端缓存此数据以备将来更改。 只有当主节点变得不可达或回复它不再持有租约时,它才需要再次联系主节点。3. 客户端将数据推送到所有副本。 客户可以按任何顺序这样做。 每个 chunkserver 都会将数据存储在内部 LRU 缓冲区缓存中,直到数据被使用或老化。 通过将数据流与控制流解耦,我们可以通过基于网络拓扑调度昂贵的数据流来提高性能,而不管哪个块服务器是主要的。 第 3.2 节对此进行了进一步讨论。4。 一旦所有副本都确认接收到数据,客户端就会向主副本发送写入请求。该请求标识了之前推送给所有副本的数据。 主节点为它接收到的所有变更分配连续的序列号,这些变更可能来自多个客户端,这提供了必要的序列化。 它以序列号顺序将突变应用到它自己的本地状态。 5. primary 将写入请求转发给所有辅助副本。 每个次要副本都按照主副本分配的相同序列号顺序应用突变。6。 secondary 都回复primary 表明他们已经完成了operation.7. 主要回复客户。 在任何副本上遇到的任何错误都会报告给客户端。在出现错误的情况下,写入可能已在主副本和辅助副本的任意子集上成功。 (如果它在主区域失败,它就不会被分配序列号并被转发。)客户端请求被认为失败,并且修改后的区域处于不一致状态。 我们的客户端代码通过重试失败的突变来处理此类错误。 它将在步骤 (3) 到 (7) 中进行几次尝试,然后再从写入开始重新尝试

如果应用程序的写入很大或跨越块边界,GFS 客户端代码会将其分解为多个写入操作。 它们都遵循上述控制流程,但可能会与其他客户端的并发操作交错并被覆盖。 因此,共享文件区域可能最终包含来自不同客户端的片段,尽管副本将是相同的,因为各个操作在所有副本上以相同的顺序成功完成。 这使得文件区域处于一致但未定义的状态,如第 2.7.3.2 节数据流中所述,我们将数据流与控制流分离以有效地使用网络。 当控制从客户端流向主服务器,然后流向所有辅助服务器时,数据以流水线方式沿着精心挑选的块服务器链线性推送。 我们的目标是充分利用每台机器的网络带宽,避免网络瓶颈和高延迟链接,并最大限度地减少推送所有数据的延迟。 , 树)。 因此,每台机器的全部出站带宽用于尽可能快地传输数据,而不是在多个接收者之间分配。为了尽可能避免网络瓶颈和高延迟链路(例如,交换机间链路通常两者兼而有之),每台机器转发 数据到网络拓扑中“最近”的机器,但没有收到它。 假设客户端正在将数据推送到块服务器 S1 到 S4。 它将数据发送到最近的块服务器,比如 S1。 S1 通过最接近 S1 的 S4 将其转发到最近的 chunkserver S2,例如 S2。 同样,S2 将其转发给 S3 或 S4,以离 S2 较近的那个为准,依此类推。 我们的网络拓扑非常简单,可以根据 IP 地址准确估计“距离”。最后,我们通过 TCP 连接上的流水线数据传输来最小化延迟。 一旦chunkserver接收到一些数据,它就会立即开始转发。 流水线对我们特别有帮助,因为我们使用具有全双工链路的交换网络。 立即发送数据不会降低接收速率。 在没有网络拥塞的情况下,将 B 个字节传输到 R 个副本的理想耗用时间是 B/T + RL,其中 T 是网络吞吐量,L 是在两台机器之间传输字节的延迟。 我们的网络链接通常是 100 Mbps (T),而 L 远低于 1 ms。因此,理想情况下,1 MB 可以在大约 80 ms 内分发。3.3 原子记录追加 GFS 提供了一个称为 recordappend 的原子追加操作。 在传统写入中,客户端指定要写入数据的偏移量。 对同一区域的并发写入不可序列化:该区域最终可能包含来自多个客户端的数据片段。 然而,在 recordappend 中,客户端仅指定数据。 GFS 以原子方式(即,作为一个连续的字节序列)将其追加到文件中 GFS 选择的偏移量,并将该偏移量返回给客户端。 这类似于在 Unix 中写入以 O APPEND 模式打开的文件,当多个写入者并发时没有竞争条件

记录追加被我们的分布式应用程序大量使用,在这些应用程序中,不同机器上的许多客户端同时追加到同一个文件。 客户端将需要额外的复杂和昂贵的同步,例如通过分布式锁管理器,如果他们使用传统的写操作。 在我们的工作负载中,此类文件通常用作多生产者/单消费者队列或包含来自许多不同客户端的合并结果。记录追加是一种突变,遵循第 3.1 节中的控制流,在主要部分仅增加了一点额外的逻辑。 客户端将数据推送到文件最后一个块的所有副本,然后将其请求发送到主副本。 主节点检查将记录附加到当前块是否会导致块超过最大大小 (64 MB)。 如果是这样,它将块填充到最大大小,告诉辅助节点也这样做,并回复客户端指示应该在下一个块上重试该操作。 (记录附加被限制为最大块大小的至多四分之一,以将最坏情况的碎片保持在可接受的水平。)如果记录适合最大大小,这是常见情况,主节点将数据附加到其副本,告诉 辅助节点将数据写入数据所在的确切偏移量,并最终向客户端回复成功。如果记录追加在任何副本失败,客户端将重试该操作。 因此,同一块的副本可能包含不同的数据,可能包括同一记录的全部或部分副本。 GFS 不保证所有副本都按字节相同。 它只保证数据作为一个原子单元至少被写入一次。 这个属性很容易从简单的观察中得出,即要使操作报告成功,数据必须在某个块的所有副本上以相同的偏移量写入。 此外,在此之后,所有副本至少与记录末尾一样长,因此任何未来的记录都将被分配更高的偏移量或不同的块,即使不同的副本成为主副本。 就我们的一致性保证而言,成功的记录追加操作写入其数据的区域是定义的(因此是一致的),而中间区域是不一致的(因此是未定义的)。 正如我们在第 2.7.2 节中讨论的那样,我们的应用程序可以处理不一致的区域

快照 快照操作几乎在瞬间制作了文件或目录树(“源”)的副本,同时最大限度地减少了正在进行的突变的任何中断。 我们的用户使用它来快速创建庞大数据集的分支副本(通常是这些副本的副本,递归地),或者在试验更改之前检查当前状态,这些更改可以在以后轻松提交或回滚。与 AFS [5] 一样,我们使用标准副本 - on-write 技术来实现快照。 当 master 收到一个快照请求时,它首先撤销对它要快照的文件中的块的任何未完成的租约。 这确保了对这些块的任何后续写入都需要与 master 交互以找到租约持有者。 这将使master 有机会首先创建块的新副本。在租约被撤销或过期后,master 将操作记录到磁盘。 然后,它通过复制源文件或目录树的元数据,将此日志记录应用于其内存状态。 新创建的快照文件指向与源文件相同的块。客户端在快照操作后第一次想要写入块 C 时,它会向 master 发送请求以查找当前的租约持有者。 master 注意到 chunkC 的引用计数大于 1。 它推迟对客户端请求的回复,而是选择一个新的块句柄 C'。 然后它要求每个拥有 C 的当前副本的块服务器创建一个名为 C' 的新块。 通过在与原始块相同的块服务器上创建新块,我们确保可以在本地复制数据,而不是通过网络(我们的磁盘大约是 100 Mb 以太网链接速度的三倍)。 从这一点来看,请求处理与对任何块的处理没有什么不同:master 授予其中一个副本对新块 C' 的租约,并回复客户端,客户端可以正常写入块,不知道它是刚刚从现有块创建的 块.4。 MASTER OPERATIONMaster 执行所有命名空间操作。 此外,它管理整个系统的块副本:它做出放置决策,创建新块和副本,并协调各种系统范围的活动以保持块完全复制,平衡所有块服务器之间的负载,并回收未使用的存储。 我们现在讨论每个主题

4.1 命名空间管理和锁定 许多主操作可能需要很长时间:例如,快照操作必须撤销快照覆盖的所有块上的块服务器租约。 我们不想在其他主操作运行时延迟它们。 因此,我们允许多个操作处于活动状态,并在命名空间的区域上使用锁以确保正确的序列化。与许多传统文件系统不同,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 上获取写锁。 这两个操作将被正确序列化,因为它们试图获得冲突的 lockson /home/user。 文件创建不需要父目录上的写锁,因为没有“目录”或类似 inode 的数据结构要保护不被修改。名称上的读锁足以保护父目录不被删除。这个的一个很好的属性 锁定方案是它允许在同一目录中进行并发突变。 例如,多个文件的创建可以在同一个目录中同时执行:每个文件都获取目录名的读锁和文件名的写锁。 目录名称上的读锁足以防止目录被删除、重命名或快照。 文件名上的写锁序列化了两次创建同名文件的尝试。由于命名空间可以有很多节点,读写锁对象被延迟分配并在不使用时被删除。 此外,以一致的总顺序获取锁以防止死锁:它们首先按名称空间树中的级别排序,并在同一级别内按字典顺序排列

4.2 副本放置 GFS 集群高度分布在多个级别。 它通常有数百个块服务器分布在许多机器机架上。 这些块服务器又可以从来自相同或不同机架的数百个客户端访问。 不同机架上的两台机器之间的通信可能会跨越一个或多个网络交换机。 此外,进出机架的带宽可能小于机架内所有机器的总带宽。多级分布对分布数据的可伸缩性、可靠性和可用性提出了独特的挑战。块副本放置策略有两个目的:最大化 数据可靠性和可用性,并最大限度地利用网络带宽。 对于两者来说,跨机器传播副本是不够的,它只能防止磁盘或机器故障并充分利用每台机器的网络带宽。 我们还必须跨机架传播块副本。 这确保即使整个机架损坏或离线(例如,由于共享资源(如网络交换机或电源电路)发生故障),块的某些副本也能存活并保持可用。 这也意味着块的流量,尤其是读取,可以利用多个机架的总带宽。 另一方面,写入流量必须流经多个机架,这是我们心甘情愿做出的权衡。 4.3 创建、重新复制、重新平衡Chunkreplicas 的创建有三个原因:chunkcreation、re-replication 和 rebalancing。当 master 创建一个 chunk 时,它选择 在哪里放置最初为空的副本。 它考虑了几个因素。 (1) 我们希望将新副本放置在磁盘空间利用率低于平均水平的块服务器上。 随着时间的推移,这将均衡跨块服务器的磁盘利用率。 (2) 我们想限制每个块服务器上“最近”创建的数量。虽然创建本身很便宜,但它可靠地预测即将到来的大量写入流量,因为块是在写入需要时创建的,并且在我们的一次追加多次读取工作负载中 一旦完全写入,它们通常会变为只读状态。 (3) 正如上面所讨论的,我们希望将块的副本分布在机架上。只要可用副本的数量低于用户指定的目标,主服务器就会重新复制块。 发生这种情况的原因有多种:块服务器变得不可用,它报告其副本可能已损坏,其中一个磁盘因错误而被禁用,或者复制目标增加。 每个需要重新复制的块都会根据几个因素确定优先级。 一是它离复制目标有多远。 例如,我们给丢失了两个副本的块赋予比只丢失一个副本的块更高的优先级。 此外,我们更喜欢首先为活动文件重新复制块,而不是属于最近删除的文件的块(参见第 4.4 节)。 最后,为了最大限度地减少故障对正在运行的应用程序的影响,我们提高了任何阻止客户端进程的块的优先级

master 选择最高优先级的 chunk 并通过指示某些 chunkserver 直接从现有的有效副本复制 chunk 数据来“克隆”它。 放置新副本的目标与创建副本的目标相似:均衡磁盘空间利用率,限制任何单个块服务器上的活动克隆操作,以及跨机架分布副本。 对于每个块服务器。 此外,每个块服务器通过限制其对源块服务器的读取请求来限制它在每个克隆操作上花费的带宽量。最后,主服务器定期重新平衡副本:它检查当前副本分布并移动副本以获得更好的磁盘空间和负载平衡。 同样通过这个过程,master 逐渐填满一个新的块服务器,而不是立即用新块和随之而来的大量写入流量淹没它。 新副本的放置标准与上面讨论的类似。 此外,master 还必须选择要删除的现有副本。 一般来说,它更喜欢删除那些低于平均可用空间的chunkservers,以平衡磁盘空间的使用。4.4 垃圾收集文件被删除后,GFS 不会立即回收可用的物理存储。 它只是在文件和块级别的常规垃圾收集期间懒惰地这样做。我们发现这种方法使系统更简单和更可靠。4.4.1 机制当应用程序删除文件时,master 会立即记录删除,就像其他更改一样。 然而,文件并没有立即回收资源,而是被重命名为包含删除时间戳的隐藏名称。 在 master 定期扫描文件系统命名空间期间,如果隐藏文件已存在超过三天(时间间隔可配置),它会删除任何此类隐藏文件。在此之前,文件仍然可以在新的特殊名称下读取并且可以恢复 通过将其重命名为正常。当隐藏文件从命名空间中删除时,其内存中的元数据将被删除。 这有效地切断了它到所有块的链接。在块名称空间的类似常规扫描中,master 识别孤立块(即那些无法从任何文件访问的块)并删除这些块的元数据。 在定期与 master 交换的 HeartBeat 消息中,每个 chunkserver 报告它拥有的块的一个子集,master 回复所有不再出现在 master 的元数据中的块的标识。 chunkserver 可以自由删除它的这些块的副本。4.4.2 讨论虽然分布式垃圾收集是一个难题,在编程语言的上下文中需要复杂的解决方案,但在我们的例子中它非常简单。 我们可以很容易地识别所有对块的引用:它们在由 master 专门维护的文件到块映射中。我们还可以轻松识别所有块副本:它们是每个块服务器上指定目录下的 Linux 文件。任何不为 master 所知的副本是“ 垃圾。”

存储回收的垃圾收集方法与急切删除相比有几个优点。 首先,它在组件故障常见的大规模分布式系统中简单可靠。 块创建可能在某些块服务器上成功,但在其他服务器上可能不会成功,从而留下主服务器不知道存在的副本。 副本删除消息可能会丢失,Master 必须记住在失败时重新发送它们,包括它自己的和 Chunk 服务器的。垃圾收集提供了一种统一且可靠的方法来清理任何未知有用的副本。 其次,它将存储回收合并到 master 的常规后台活动中,例如定期扫描名称空间和与块服务器握手。 因此,它是分批完成的,成本是摊销的。 而且,只有当主人相对空闲时才会这样做。 主人可以更迅速地响应需要及时关注的客户请求。 第三,回收存储的延迟提供了防止意外、不可逆删除的安全网。根据我们的经验,主要缺点是延迟有时会阻碍用户在存储紧张时微调使用的努力。 重复创建和删除临时文件的应用程序可能无法立即重新使用存储。 如果已删除的文件被再次明确删除,我们通过加快存储回收来解决这些问题。 我们还允许用户对命名空间的不同部分应用不同的复制和回收策略。 例如,用户可以指定某个目录树中的文件中的所有块都将在没有复制的情况下存储,并且任何删除的文件都会立即且不可撤销地从文件系统状态中删除。 4.5 陈旧副本检测如果块服务器发生故障并且错过了更改到 大块,而它是下来。 对于每个块,master 维护一个块版本号以区分最新和陈旧的副本。每当 master 授予一个块的新租约时,它会增加块版本号并通知最新的副本。 主服务器和这些副本都在其持久状态中记录新版本号。 这发生在任何客户端被通知之前,因此在它可以开始写入块之前。 如果另一个副本当前不可用,它的块版本号将不会被提升。 当 chunkserver 重启并报告它的 chunks 集合和它们相关的版本号时,master 会检测到这个 chunkserver 有一个陈旧的副本。 如果 master 发现 aversion number 大于其记录中的版本号,master 会认为它在授予租约时失败了,因此将更高版本更新为最新版本。master 在其常规垃圾收集中删除陈旧的副本。 在此之前,当它回复客户端对块信息的请求时,它实际上认为陈旧的副本根本不存在。 作为另一种保护措施,master 在通知客户端哪个 chunkserver 持有一个 chunk 的租约时,或者当它指示一个 chunkserver 在克隆操作中从另一个 chunkserver 读取该 chunk 时,包括 chunkversion 号。 客户端或块服务器在执行操作时验证版本号,以便它始终访问最新的数据。 5. 容错和诊断 我们在设计系统时面临的最大挑战之一是处理频繁的组件故障。 组件的质量和数量共同使这些问题成为常态而非例外:我们不能完全信任机器,也不能完全信任磁盘。 组件故障可能导致系统不可用,或者更糟糕的是,数据损坏。 我们讨论我们如何应对这些挑战以及我们在系统中内置的工具,以便在问题不可避免地发生时进行诊断

5.1 高可用性 在 GFS 集群中的数百台服务器中,有些服务器在任何给定时间都必然不可用。 我们通过两个简单而有效的策略保持整个系统的高可用性:快速恢复和复制。5.1.1 快速恢复主服务器和块服务器都旨在恢复其状态并在几秒钟内启动,无论它们如何终止。 事实上,我们不区分正常终止和异常终止; 服务器通常只是通过终止进程来关闭。 客户端和其他服务器在未完成的请求超时、重新连接到重新启动的服务器并重试时会遇到轻微的问题。 6.2.2 节报告观察到的启动时间。5.1.2 块复制如前所述,每个块都在不同机架上的多个块服务器上进行复制。 用户可以为文件命名空间的不同部分指定不同的复制级别。默认为三个。 当 chunkservers 离线或通过校验和验证(参见第 5.2 节)检测损坏的副本时,主节点根据需要克隆现有副本以保持每个块完全复制。 尽管复制对我们很有帮助,但我们正在探索其他形式的跨服务器冗余,例如奇偶校验或纠删码,以满足我们不断增加的只读存储需求。 我们预计在我们非常松散耦合的系统中实施这些更复杂的冗余方案具有挑战性但可以管理,因为我们的流量主要由附加和读取而不是小的随机写入。5.1.3 主复制复制主状态以提高可靠性。 它的操作日志和检查点被复制到多台机器上。 只有在其日志记录已刷新到磁盘本地和所有主副本上后,才认为对该状态的更改已提交。 为简单起见,一个主进程仍然负责所有变更以及后台活动,例如在内部更改系统的垃圾收集。当它失败时,它几乎可以立即重新启动。 如果它的机器或磁盘出现故障,GFS 外部的监控基础设施会在别处启动一个新的主进程,并复制操作日志。 客户端仅使用 master 的规范名称(例如 gfs-test),这是一个 DNS 别名,如果将 master 重新定位到另一台机器,则可以更改该别名。此外,“影子”master 提供对文件系统的只读访问,即使 初级主机已关闭。 它们是影子,而不是镜子,因为它们可能会稍微滞后于初级,通常是几分之一秒。 它们增强了未被主动改变的文件或不介意获得稍微陈旧结果的应用程序的可读性。事实上,由于文件内容是从块服务器读取的,因此应用程序不会观察到陈旧的文件内容。 在短窗口内可能过时的是文件元数据,如目录内容或访问控制信息。为了让自己了解情况,影子主控读取不断增长的操作日志的副本,并将与主控完全相同的更改序列应用于其数据结构。就像 主要的,它在启动时(之后很少)轮询块服务器以定位块副本并与它们交换频繁的握手消息以监视它们的状态。 它仅依赖主 master 来进行由 primary 决定创建和删除副本的副本位置更新

5.2 数据完整性每个块服务器使用校验和来检测存储数据的损坏。 鉴于 GFS 集群通常在数百台机器上有数千个磁盘,它经常会遇到磁盘故障,导致读取和写入路径上的数据损坏或丢失。 (一个原因参见第 7 节。)我们可以使用其他块副本从损坏中恢复,但是通过跨块服务器比较副本来检测损坏是不切实际的。 此外,不同的副本可能是合法的:GFS 突变的语义,特别是前面讨论的原子记录追加,不保证相同的副本。 因此,每个块服务器必须通过维护校验和来独立验证自己副本的完整性。一个块被分成 64 KB 的块。 每个都有相应的 32 位校验和。 与其他元数据一样,校验和保存在内存中并与日志记录永久存储,与用户数据分开。对于读取,chunkserver 在将任何数据返回给请求者(无论是客户端还是另一个 chunkserver)之前验证与读取范围重叠的数据块的校验和。因此,chunkservers 不会将损坏传播到其他机器。 如果一个块与记录的校验和不匹配,chunkserver 会向请求者返回一个错误,并向 master 报告不匹配。 作为响应,请求者将从其他副本读取,而主服务器将从另一个副本克隆块。 在一个有效的新副本就位后,master 指示报告不匹配的 chunkserver 删除它的副本。出于几个原因,校验和对读取性能几乎没有影响。 由于我们的大部分读取至少跨越几个块,因此我们只需要读取和校验和校验相对少量的额外数据以进行验证。 GFS 客户端代码通过尝试对齐读取块边界来进一步减少这种开销。 此外,chunkserver 上的校验和查找和比较是在没有任何 I/O 的情况下完成的,并且校验和计算通常可以与 I/O 重叠。校验和计算针对附加到块末尾的写入(与覆盖现有数据的写入相反)进行了大量优化,因为 它们在我们的工作负载中占主导地位。 我们只是增量地更新最后一个部分校验和块的校验和,并计算由附加填充的任何全新校验和块的新校验和。 即使最后一个部分校验和块已经损坏并且我们现在无法检测到它,新的校验和值也不会匹配存储的数据,并且在下一次读取块时将像往常一样检测到损坏相反,如果写入覆盖现有范围 对于块,我们必须读取并验证被覆盖范围的第一个和最后一个块,然后执行写入,最后计算并记录新的校验和。 如果我们在部分覆盖之前不验证第一个和最后一个块,新的校验和可能会隐藏未被覆盖区域中存在的损坏。在空闲期间,块服务器可以扫描和验证非活动块的内容。 这使我们能够检测很少读取的块中的损坏。 一旦检测到损坏,master 就可以创建一个新的未损坏的副本并删除损坏的副本。 这可以防止不活动但损坏的块副本欺骗主服务器认为它有足够的有效块副本

5.3 诊断工具广泛而详细的诊断日志记录对问题隔离、调试和性能分析有不可估量的帮助,同时只产生最小的成本。 没有日志,就很难理解机器之间短暂的、不可重复的交互。 GFS 服务器生成诊断日志,记录许多重要事件(例如 chunk 服务器启动和关闭)和所有 RPC 请求和回复。 这些诊断日志可以随意删除而不影响系统的正确性。 但是,我们尽量在空间允许的范围内保留这些日志。RPC 日志包括在线路上发送的确切请求和响应,正在读取或写入的文件数据除外。 通过匹配请求与回复并整理不同机器上的 RPCrecords,我们可以重建整个交互历史来诊断问题。 日志还用作负载测试和性能分析的跟踪。日志记录对性能的影响是最小的(并且远远超过好处),因为这些日志是顺序和异步写入的。 最近的事件也保存在内存中,可用于连续在线监控。6。 测量在本节中,我们提出了一些微基准来说明 GFS 架构和实现中固有的瓶颈,以及一些来自谷歌使用的真实集群的一些数据。6.1 微基准我们测量了一个 GFS 集群的性能,该集群由一个主服务器、两个主副本、 16 个块服务器和 16 个客户端。 请注意,设置此配置是为了便于测试。 典型的集群有数百个块服务器和数百个客户端。所有机器都配置有双 1.4 GHz PIII 处理器、2 GB 内存、两个 80 GB 5400 rpm 磁盘和一个到 HP 2524 交换机的 100 Mbps 全双工以太网连接。 所有 19 个 GFS 服务器机器都连接到一个交换机,所有 16 个客户端机器都连接到另一个。 这两个交换机通过 1 Gbps 链路连接。6.1.1 ReadsN 客户端同时从文件系统读取。 每个客户端从 320 GB 的文件集中读取一个随机选择的 4 MB 区域。 这将重复 256 次,以便每个客户端最终读取 1 GB 的数据。 chunkservers 加在一起只有 32 GB 的内存,所以我们预计 Linux 缓冲区缓存的命中率最多为 10%。 我们的结果应该接近冷缓存结果

图 3(a) 显示了 N 个客户端的总读取率及其理论限制。 当两个交换机之间的 1 Gbps 链路饱和时,限制峰值达到 125 MB/s,或者当其 100 Mbps 网络接口饱和时,每个客户端达到 12.5 MB/s,以适用者为准。 当只有一个客户端在读取时,观察到的读取速率为 10 MB/s,或每个客户端限制的 80%。 总读取速率达到 94 MB/s,大约是 125 MB/s 链接限制的 75%,对于 16 个读取器,或每个客户端 6 MB/s。 效率从 80% 下降到 75%,因为随着读者数量的增加,多个读者同时从同一个 chunkserver 读取的概率也在增加。6.1.2 WritesN 个客户端同时写入 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),大约是理论极限的一半。 与读取的情况一样,随着客户端数量的增加,多个客户端并发写入同一个 chunkserver 的可能性更大。 此外,16 位写入者比 16 位读取者更容易发生冲突,因为每次写入都涉及三个不同的副本。写入速度比我们希望的要慢。 在实践中,这并不是一个主要问题,因为即使它增加了单个客户端所看到的延迟,它也不会显着影响系统向大量客户端提供的总写入带宽。6.1.3 记录追加图 3(c) 显示了记录 附加性能。 N 个客户端同时附加到一个文件。 性能受限于存储文件最后一块的块服务器的网络带宽,与客户端数量无关。 对于一个客户端,它从 6.0 MB/s 开始,对于 16 个客户端,它下降到 4.8 MB/s,这主要是由于不同客户端看到的网络传输速率的拥塞和差异。我们的应用程序倾向于同时生成多个此类文件。 换句话说,N个客户端同时附加到M个共享文件,其中N和M都是几十或几百。 因此,我们实验中的 chunkserver 网络拥塞在实践中并不是一个重要问题,因为客户端可以在写入一个文件的过程中取得进展,而另一个文件的 chunkservers 却很忙。6.2 真实世界的集群我们现在检查谷歌内部使用的两个集群,它们代表了其他几个集群 像他们。 集群 A 被一百多名工程师定期用于研究和开发。 一个典型的任务由人类用户发起,并运行长达数小时。 它读取几 MB 到几 TB 的数据,转换或分析数据,并将结果写回集群。 Cluster B主要用于生产数据处理。 这些任务持续时间更长,并且持续生成和处理多 TB 数据集,仅偶尔需要人工干预。 在这两种情况下,一个“任务”由许多机器上的许多进程组成,同时读取和写入许多文件

6.2.1 存储如表中的前五个条目所示,两个集群都有数百个块服务器,支持许多 TB 的磁盘空间,并且相当但不是完全满。 “已用空间”包括所有块副本。 几乎所有文件都被复制了三次。 因此,这两个集群分别存储了 18 TB 和 52 TB 的文件数据。这两个集群具有相似数量的文件,但死文件的比例更大,即被删除或被新版本替换但其存储尚未被回收的文件。 它也有更多的块,因为它的文件往往更大。 6.2.2 元数据 块服务器总共存储数十 GB 的元数据,主要是 64 KB 用户数据块的校验和。块服务器中唯一保存的其他元数据是块版本号 第 4.5 节。保存在 master 中的元数据要小得多,只有几十 MB,或者平均每个文件大约 100 字节。 这与我们的假设一致,即 master 内存的大小在实践中不会限制系统的容量。大多数每个文件的元数据是以前缀压缩形式存储的文件名。 其他元数据包括文件所有权和权限、从文件到块的映射以及每个块的当前版本。 此外,对于每个块,我们存储当前副本位置和用于实现写时复制的引用计数。每个单独的服务器,包括块服务器和主服务器,只有 50 到 100 MB 的元数据。 因此恢复速度很快:在服务器能够回答查询之前从磁盘读取此元数据只需要几秒钟。 然而,master 在一段时间内有点受阻——通常是 30 到 60 秒——直到它从所有块服务器获取了块位置信息。6.2.3 读写速率表 3 显示了不同时间段的读写速率。 在进行这些测量时,两个集群都已运行了大约一周。 (最近重启了集群以升级到新版本的 GFS。)重启后平均写入速率低于 30 MB/s。 当我们进行这些测量时,B 正处于写入活动的突发中间,生成大约 100 MB/sof 数据,这产生了 300 MB/s 的网络负载,因为写入被传播到三个副本

图 3:聚合吞吐量。 顶部曲线显示了我们的网络拓扑施加的理论限制。 底部曲线显示测量的吞吐量。 他们有显示 95% 置信区间的误差条,在某些情况下由于测量方差小而难以辨认 表 3:两个 GFS 集群的性能指标读取速率远高于写入速率。总工作负载由读取多于写入组成 正如我们假设的那样。 两个集群都处于大量读取活动的中间。 特别是,A 在前一周一直保持 580 MB/s 的读取率。 它的网络配置可以支持 750 MB/s,因此它可以有效地利用其资源。 集群 B 可以支持 1300 MB/s 的峰值读取速率,但其应用程序仅使用 380 MB/s。6.2.4 Master LoadTable 3 还显示发送到 master 的操作速率约为每秒 200 到 500 次操作。 master 可以轻松跟上这个速度,因此不是这些工作负载的瓶颈。在早期版本的 GFS 中,master 偶尔会成为某些工作负载的瓶颈。 它花费大部分时间依次扫描大型目录(其中包含数十万个文件)以查找特定文件。 从那以后,我们更改了主数据结构,以允许通过命名空间进行高效的二进制搜索。 它现在可以轻松支持每秒数千次文件访问。 如果有必要,我们可以通过在名称空间数据结构前面放置名称查找缓存来进一步加快速度。6.2.5 恢复时间在一个块服务器发生故障后,一些块将变得复制不足,必须克隆以恢复它们的复制级别。 恢复所有此类块所需的时间取决于资源量。 在一个实验中,我们杀死了集群 B 中的一个 chunkserver。该 chunkserver 有大约 15,000 个包含 600 GB 数据的块。 为了限制对正在运行的应用程序的影响并为调度决策提供余地,我们的默认参数将此集群限制为 91 个并发克隆(块服务器数量的 40%),其中每个克隆操作最多允许消耗 6.25 MB/s(50 Mbps) ). 所有块都在 23.2 分钟内恢复,有效复制速率为 440 MB/s。在另一个实验中,我们杀死了两个块服务器,每个块服务器大约有 16,000 个块和 660 GB 的数据。 这种双重故障将 266 个块减少为具有单个副本。 这 266 个块以更高的优先级克隆,并在 2 分钟内全部恢复到至少 2x 复制,从而使集群处于可以容忍另一个块服务器故障而不会丢失数据的状态。6.3 工作负载分解在本节中,我们详细介绍了工作负载 在两个 GFS 集群上,与第 6.2 节中的类似但不相同。 集群 X 用于研究和开发,而集群 Y 用于生产数据处理。6.3.1 方法论和注意事项这些结果仅包括客户端发起的请求,因此它们反映了我们的应用程序为整个文件系统生成的工作负载。 它们不包括执行客户端请求或内部后台活动的服务器间请求,例如转发写入或重新平衡。I/O 操作的统计信息基于从 GFS 服务器记录的实际 RPC 请求中启发式重建的信息。 例如,GFS 客户端代码可能会将读取分成多个 RPC 以增加并行性,我们从中推断出原始读取。 由于我们的访问模式是高度程式化的,我们希望任何错误都在噪音中。 应用程序的显式日志记录可能提供了稍微更准确的数据,但从逻辑上讲,重新编译和重新启动数千个正在运行的客户端来这样做是不可能的,而且从尽可能多的机器上收集结果也很麻烦

人们应该注意不要过度概括我们的工作量。 由于谷歌完全控制了 GFS 及其应用程序,因此应用程序倾向于针对 GFS 进行调优,而反过来 GFS 就是为这些应用程序设计的。 这种相互影响也可能存在于一般应用程序之间 表 4:按规模(%)划分的操作细分。 对于读取,大小是实际读取和传输的数据量,而不是请求的数据量和文件系统,但在我们的案例中效果可能更明显。6.3.2 块服务器工作负载表 4 显示了按大小划分的操作分布。 读取大小呈现双峰分布。 小读取(小于 64 KB)来自搜索密集型客户端,这些客户端在大文件中查找小块数据。 大量读取(超过 512 KB)来自整个文件的长时间顺序读取。大量读取在集群 Y 中根本不返回任何数据。我们的应用程序,尤其是生产系统中的应用程序,通常使用文件作为生产者-消费者队列。 生产者同时追加到文件,而消费者读取文件末尾。 有时,当消费者超过生产者时,不会返回任何数据。 集群 X 很少显示这种情况,因为它通常用于短期数据分析任务,而不是长期分布式应用程序。写入大小也呈现双峰分布。 大型写入(超过 256 KB)通常是由写入器中的大量缓冲引起的。 缓冲区较少的数据、检查点或更频繁地同步,或者只是为较小的写入(小于 64 KB)生成较少数据帐户的写入器。至于记录追加,集群 Y 看到的大记录追加百分比比集群 X 高得多,因为我们的生产系统 ,它使用集群 Y,针对 GFS 进行了更积极的调整。表 5 显示了在各种规模的操作中传输的数据总量。 对于所有类型的操作,较大的操作(超过 256 KB)通常占传输字节的大部分。 由于随机查找工作负载,小型读取(小于 64 KB)确实会传输一小部分读取数据。6.3.3 附加与 WritesRecord 附加被大量使用,尤其是在我们的生产系统中。 对于集群 X,写入与 recordappends 的比率按传输字节数为 108:1,按操作计数为 8:1。 对于生产系统使用的集群 Y,比率分别为 3.7:1 和 2.5:1。 此外,这些比率表明对于这两个集群,记录追加往往大于写入。 然而,对于集群 X,在测量期间记录追加的总体使用率相当低,因此结果可能会受到一两个具有特定缓冲区大小选择的应用程序的影响。正如预期的那样,我们的数据突变工作负载主要是追加而不是覆盖。 我们测量了主副本上覆盖的数据量。 此 ap 表 5:按操作大小 (%) 传输的字节数细分。 对于读取,大小是实际读取和传输的数据量,而不是请求的数据量。 如果读取尝试读取文件末尾之外的内容,这两者可能会有所不同,这在我们的工作负载中并不少见 表 6:按类型划分的主请求细分 (%) 近似于客户端故意覆盖以前写入的数据而不是附加新数据的情况。 对于集群 X,覆盖占字节突变的 0.0001% 以下和突变操作的 0.0003% 以下。 对于集群 Y,比率均为 0.05%。 虽然这很小,但仍然高于我们的预期。 事实证明,这些覆盖中的大部分来自客户端因错误或超时而重试。 它们本身不是工作负载的一部分,而是重试机制的结果。6.3.4 主工作负载表 6 显示了按向主请求类型划分的细分。 大多数请求请求块位置 (FindLocation) 读取和租赁持有人信息 (FindLeaseLocker) 数据突变。集群 X 和 Y 看到明显不同的删除请求数量,因为集群 Y 存储定期重新生成并替换为更新版本的生产数据集。 这种差异的一部分进一步隐藏在打开请求的差异中,因为旧版本的文件可能会通过从头开始写入而被隐式删除(Unix 开放术语中的模式“w”)。FindMatchingFiles 是一种模式匹配请求,支持“ls” 和类似的文件系统操作。 与 master 的其他请求不同,它可能处理命名空间的很大一部分,因此可能很昂贵。 集群 Y 更频繁地看到它,因为自动化数据处理任务倾向于检查文件系统的各个部分以了解全局应用程序状态。 相比之下,集群 X 的应用程序受到更明确的用户控制,并且通常提前知道所有需要的文件的名称

  1. 经验 在构建和部署 GFS 的过程中,我们遇到了各种各样的问题,一些是操作上的,一些是技术上的。最初,GFS 被设想为我们生产系统的后端文件系统。 随着时间的推移,用法演变为包括研究和开发任务。 它开始时对权限和配额之类的东西支持很少,但现在包括这些的基本形式。 虽然生产系统受到良好的纪律和控制,但用户有时却并非如此。 需要更多的基础设施来防止用户相互干扰。我们的一些最大问题与磁盘和 Linux 相关。我们的许多磁盘向 Linux 驱动程序声称它们支持一系列 IDE 协议版本,但实际上只对更新的版本做出可靠响应 . 由于协议版本非常相似,这些驱动器大部分都可以工作,但偶尔不匹配会导致驱动器和内核对驱动器的状态不一致。 由于内核中的问题,这会无声地破坏数据。 这个问题促使我们使用校验和来检测数据损坏,同时我们修改了内核以处理这些协议不匹配。由于 fsync() 的成本,我们在 Linux 2.2 内核之前遇到了一些问题。 它的成本与文件的大小成正比,而不是与修改部分的大小成正比。 这对我们的大型操作日志来说是一个问题,尤其是在我们实施检查点之前。 我们通过使用同步写入解决了这个问题,并最终迁移到 Linux 2.4。另一个 Linux 问题是单个读写器锁,地址空间中的任何线程在从磁盘调入页面(读取器锁)或修改 mmap 中的地址空间时都必须持有该锁 () 调用(写入器锁)。 我们在轻负载下看到了系统中的短暂超时,并努力寻找资源瓶颈或零星的硬件故障。 最终,我们发现这个锁阻止了主网络线程将新数据映射到内存,而磁盘线程正在对先前映射的数据进行分页。由于我们主要受网络接口而不是内存复制带宽的限制,我们通过将 mmap() 替换为 pread( ) 以额外的副本为代价。尽管偶尔会出现问题,但 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、明尼苏达州的 GFS [11] 和 GPFS [10] 集中式服务器并依靠分布式算法来实现一致性和管理。 我们选择集中式方法以简化设计、提高可靠性并获得灵活性。 特别是,集中式主控使得实施复杂的块放置和复制策略变得更加容易,因为主控已经拥有大部分相关信息并控制其更改方式。 我们通过保持主状态较小并在其他机器上完全复制来解决容错问题。 可扩展性和高可用性(对于读取)目前由我们的影子主机制提供。 通过附加到预写日志,使对主状态的更新持久化。 因此,我们可以采用像 Harp [7] 中的那样的主副本方案,以提供比我们当前方案更强的一致性保证的高可用性。我们正在解决一个类似于 Lustre [8] 的问题,即向大量客户端提供聚合性能。 但是,我们通过关注应用程序的需求而不是构建符合 POSIX 的文件系统来显着简化问题。 此外,GFS 假设大量不可靠的组件和容错是我们设计的核心。GFS 最类似于 NASD 架构 [4]。虽然 NASD 架构基于网络连接的磁盘驱动器,但 GFS 使用商品机器作为块服务器,如 NASD 原型。 与 NASD 的工作不同,我们的块服务器使用延迟分配的固定大小块而不是可变长度对象。 此外,GFS 实现了生产环境所需的再平衡、复制和恢复等特性。与明尼苏达州的 GFS 和 NASD 不同,我们不寻求改变存储设备的模型。 我们专注于解决具有现有商品组件的复杂分布式系统的日常数据处理需求。原子记录附加支持的生产者-消费者队列解决了与 River [2] 中的分布式队列类似的问题。 River 使用跨机器分布的基于内存的队列和仔细的数据流控制,而 GFS 使用可以由许多生产者同时附加的持久文件。 River 模型支持 m-to-n 分布式队列,但缺乏持久存储的容错能力,而 GFS 仅有效支持 m-to-1 队列。 多个消费者可以读取同一个文件,但他们必须协调来划分传入的 load.9。 结论 Google 文件系统展示了在商用硬件上支持大规模数据处理工作负载所必需的品质。 虽然一些设计决策特定于我们的独特设置,但许多可能适用于类似规模和成本意识的数据处理任务。我们首先根据我们当前和预期的应用程序工作负载和技术环境重新检查传统文件系统假设。 我们的观察导致了设计空间中截然不同的点。我们将组件故障视为常态而不是例外,针对大部分附加(可能同时)然后读取(通常顺序)的大文件进行优化,并且扩展和放宽标准 改进整体系统的文件系统接口

我们的系统通过持续监控、复制关键数据以及快速自动恢复来提供容错能力。 Chunkreplication 允许我们容忍 chunkserver 故障。 这些故障的频率激发了一种新颖的在线修复机制,该机制定期且透明地修复损坏并尽快补偿丢失的副本。 此外,我们使用校验和来检测磁盘或 IDE 子系统级别的数据损坏,考虑到系统中的磁盘数量,这种情况变得太常见了。我们的设计为执行各种任务的许多并发读取器和写入器提供了高聚合吞吐量。我们通过分离文件来实现这一点 系统控制,通过master,数据传输,直接在chunkservers和clients之间传递。 大块大小和块租用将主节点参与常见操作的程度降到最低,块租用将权限委托给数据突变中的主副本。 这使得一个不会成为瓶颈的简单、集中的主机成为可能。我们相信,我们网络堆栈的改进将解除当前对单个客户端所见写入吞吐量的限制。GFS 已成功满足我们的存储需求,并在谷歌内部广泛使用,作为 用于研发和生产数据处理的存储平台。 它是一个重要的工具,使我们能够在整个网络的规模上继续创新和解决问题。致谢我们要感谢以下人员对系统或论文的贡献。 Brain Bershad(我们的牧羊人)和匿名审稿人给了我们宝贵的意见和建议。 Anurag Acharya、Jeff Dean 和 David desJardins 为早期设计做出了贡献。 Fay Chang 致力于比较跨块服务器的副本。 Guy Edjlali 负责存储配额。 Markus Gutschke 致力于测试框架和安全性增强。 DavidKramer 致力于性能增强。 Fay Chang、Urs Hoelzle、Max Ibel、Sharon Perl、Rob Pike 和 DebbyWallach 对本文的早期草稿进行了评论。 我们在谷歌的许多同事勇敢地将他们的数据托付给了一个新的文件系统,并给了我们有用的反馈。 Yoshka 帮助进行了早期测试

REFERENCES

[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.

参考 [1] Thomas Anderson、Michael Dahlin、Jeanna Neefe、David Patterson、Drew Roselli 和 Randolph Wang。 无服务器网络文件系统。 在第 15 届 ACM 操作系统原理研讨会论文集中,第 109-126 页,科罗拉多州铜山度假村,1995 年 12 月。

[2] Remzi H. Arpaci-Dusseau、Eric Anderson、Noah Treuhaft、David E. Culler、Joseph M. Hellerstein、David Patterson 和 Kathy Yelick。 River 的集群 I/O:使快速案例变得普遍。 第六届并行和分布式系统输入/输出研讨会论文集 (IOPADS ’99),第 10-22 页,佐治亚州亚特兰大,1999 年 5 月。

[3] Luis-Felipe Cabrera 和 Darrell D. E. Long。 Swift:使用分布式磁盘条带化提供高 I/O 数据速率。 计算机系统,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 和 Jim Zelenka。 具有成本效益的高带宽存储架构。 在第 8 届编程语言和操作系统架构支持会议记录中,第 92-103 页,加利福尼亚州圣何塞,1998 年 10 月。

[5] 约翰·霍华德、迈克尔·卡扎尔、雪莉·梅内斯、大卫·尼科尔斯、马哈德夫·萨蒂亚纳拉亚南、罗伯特·西德博瑟姆和迈克尔·韦斯特。 分布式文件系统中的规模和性能。 ACM 计算机系统交易,6(1):51–81,1988 年 2 月。

[6] 间奏曲。 http://www.inter-mezzo.org,2003 年。

[7] Barbara Liskov、Sanjay Ghemawat、Robert Gruber、Paul Johnson、Liuba Shrira 和 Michael Williams。 Harp 文件系统中的复制。 在第 13 届操作系统原理研讨会上,第 226-238 页,加利福尼亚州太平洋丛林市,1991 年 10 月。

[8] 光泽。 http://www.lustreorg,2003 年。

[9] 大卫·A·帕特森、加思·A·吉布森和兰迪·H·卡茨。 廉价磁盘冗余阵列 (RAID) 的案例。 在 1988 年 ACM SIGMOD 数据管理国际会议记录中,第 109-116 页,芝加哥,伊利诺伊州,1988 年 9 月。

[10] 弗兰克施穆克和罗杰哈斯金。 GPFS:用于大型计算集群的共享磁盘文件系统。 在第一届 USENIX 文件和存储技术会议记录中,第 231-244 页,蒙特雷,加利福尼亚州,2002 年 1 月。

[11] Steven R. Soltis、Thomas M. Ruwart 和 Matthew T. O’Keefe。 全球文件系统。 在第五届美国宇航局戈达德太空飞行中心海量存储系统和技术会议记录中,马里兰州大学公园市,1996 年 9 月。

[12] Chandramohan A. Thekkath、Timothy Mann 和 Edward K. Lee。 Frangipani:一个可扩展的分布式文件系统。 在第 16 届 ACM 操作系统原理研讨会论文集中,第 224-237 页,法国圣马洛,1997 年 10 月。

Author 晓兵

首发链接: https://blog.csdn.net/ssbandjl

博客: https://logread.cn | https://blog.csdn.net/ssbandjl

weixin: ssbandjl

公众号: 云原生云

云原生云