Google 分布式系统三大论文(一)The Google File System
The Google File System 中文版
摘要
我们开发并构建了一个Google文件系统,在基于分布式架构的数据密集型应用需求下进行了设计与实现。该系统采用可扩展架构,在经济实用的日常硬件设备基础上可靠地提供了容错功能,并为众多客户端提供了卓越的整体性能。
尽管与许多先前的分布式文件系统存在相同的目标,然而我们的设计已受到应用负载状况和技术环境的影响,并将在当前及未来预期中体现出与其早期分布式文件系统的设想发生显著分离,这种差异促使我们重新评估传统文件系统在设计上的选择,探索更为根本的不同设计理念
该系统成功实现了我们的存储目标。该存储平台在Google内部得到了广泛应用,并负责生成和处理大量数据资源。这些数据资源不仅服务于我们的服务运行,还被用于支持需要处理大规模数据集的研究与开发项目。目前最大规模的集群采用了超过一千台服务器并配置了数千个硬盘,总共提供了数百TB的可用存储空间。此外, 该集群还支持数百个客户端同时在线访问这些海量数据资源
在本论文中, 我们开发了一个支持分布式应用的文件系统接口. 被讨论了各种设计细节. 最后对小规模基准测试和真实使用情况进行了评估报告.
常用术语
设计,可靠性,性能,测量
关键词
容错,可伸缩性,数据存储,集群存储
1. 简介
为了解决Google日益增长的数据处理需求, 我们开发并推出了Google文件系统(Google File System-GFS)。其目标与早期分布式文件系统的初衷高度一致, 包括性能、可扩展性、可靠性和可用性等核心指标。然而, 由于考虑到我们应用所处的负载环境和技术背景, 现阶段及未来一段时间内体现出与早期设想的巨大不同, 这一发现促使我们重新评估传统文件系统的设计理念, 并在架构上进行了全面革新
首先
其次,在按照常规的标准下来看待我们的数据存储时会发现它们都非常庞大。常见的数据存储情况是每个存储单位都达到了几GB的程度,并且每个这样的存储单位通常会包含大量的应用程序对象。例如,在处理一个由数亿个对象构成、快速增长中的庞大数据集合时(即规模达到TB级别的),即使这些系统能够支持较大的存储空间管理需求,在处理这些数量级的数据时也会变得不切实际
第三部分中提到的主要更新策略包括:绝大多数文件的更新操作采用追加新数据的方式进行处理;而文件内部的数据以顺序方式更新的情况极为少见。完成之后的数据仅能进行读取操作,并且通常仅支持顺序读取。这些类型的数据显示出以下特点:一部分可能构成用于分析的大规模数据集合;另一些可能来自运行中的应用程序产生的连续流数据;另外一些可能是档案形式的数据;还有一类可能是由一台机器生成后,在另一台机器上进行处理并使用的中间数据。在面对海量文件操作需求时,确保系统性能优化与事务一致性成为关键点之一,在这一框架下对存储层进行优化将显得尤为重要。值得注意的是,在这一过程中客户端对于缓存机制并无特别关注
第四部分旨在通过增强灵活性来促进应用程序与文件系统API协同设计的优化。例如,在不遵循GFS的一致性模型要求的情况下,在不影响整体效能的前提下显著地降低了系统的复杂度,并提升了性能效率。为了实现这一目标,在不影响整体效能的前提下显著地降低了系统的复杂度,并提升了性能效率
目前根据不同需求布置了多套GFS集群系统。其中规模最大的集群组由1,000多台服务器构成,并拥有3,645GB的硬盘容量;这些资源每天都被来自不同服务器的数百个客户端持续不断地访问
2.设计概述
2.1 假设
在构建能够适应我们的需求的文件系统时, 我们受到了这种现实的影响或指导: 我们的假设有潜在优势也有困难. 我们之前提及了一些关键关注点; 现在对我们的假设有更为详细的阐述.
该系统主要由大量经济型日常零件组装而成,在这种配置下经常发生故障。该系统必须定期检测故障状况,并及时处理故障以修复损坏的部件,在最短时间内恢复正常运行状态。
该系统负责存储一定规模的大规模文档。预计将存在数百万份电子文档,其体积一般不低于100MB。大量体积达数GB的数据文档普遍存在,并且应当得到妥善管理。此外,该系统还应具备管理小型电子文档的能力,在此情况下无需额外进行性能调优工作。
系统的工作负载主要包含两种类型的读操作:大规模的流式数据传输与小规模的数据抽样查询。在大规模流式传输中,每次操作都会加载数百KB至1MB甚至更大的数据块;而较小规模的任务则会在任意位置执行几KB范围内的数据获取。从同一个客户端设备发起的一系列连续操作往往会一次性处理文件中的一个连续区域;相比之下,在较小规模的任务处理中,则不需要严格的顺序性要求,并且可以通过适当的方式优化其执行效率以减少来回访问的情况发生
系统的任务负载不仅包含大量按顺序的操作,并且涉及对文件进行追加数据的大规模、连续性较强的写入动作。通常情况下,此类操作与读取操作在规模上具有相似性。一旦数据被写入到存储介质后,该文件很少会被再次修改。然而,在这种情况下,系统仍然提供了一种可在文件任意位置执行的小型插入动作(即小规模更新),但这些小型的操作并不需要具备高效的性能特征
系统的实现必须具备高效率和清晰度,并且要准确理解其功能意义(alex注:well-defined)。
系统整体性能中具有长期稳定性的传输速率相较于快速响应的时间敏感度更为关键。我们的大多数目标程序对于高吞吐量的数据流量处理能力极为关注,在单个读取或 writes 操作上的时间敏感度较低。
2.2 接口
GFS提供了通用的文件系统接口方案,在未采用与POSIX相似的标准API架构的情况下仍保持了较高的可用性。基于层次结构的目录模型构建了文件存储空间,并通过路径命名的方式进行标识。我们实现了基本操作功能模块,并包括但不限于创建、删除、打开等基本操作功能模块
此外,在GFS中包含快照复制和追加记录操作。快照复制以极低的成本生成文件或目录树结构的一个 deepcopy副本。记录追加操作允许多个客户端同时对单个文件执行追加操作,并确保每个客户端单独执行的行为具有原子性。无需额外锁机制即可同时进行数据追加操作。我们发现这些类型的文件对于构建大型分布应用是非常重要的。快照和记录追加操作将在3.4节和3.3节分别作进一步讨论。
2.3 架构
该GFS集群包含单一逻辑主节点(注释内容同上),这些集群通常由多台客户端设备访问(如图1所示)。其中每一台设备通常都运行着基于用户级别的Linux服务进程。在资源允许的情况下,在保证系统稳定性的前提下(即要求设备具备足够的计算能力,并且能够容忍偶尔出现的应用异常),我们可以将存储块服务器与客户端程序部署在同一台物理主机上。
所有文件都被划分为固定大小的Block结构。当创建这些Block时, Master系统会为每个Block分配一个不可变且全局唯一的64位唯一标识符用于标识。ChunkServer将这些数据存储为Linux类型的文件于本地硬盘中,并根据指定的Block ID和数据段长度进行读取和写入操作。为了确保数据可靠性与可用性,在备份完成后,默认情况下我们会备份三个副本;不过对于特定的应用场景或命名空间区域来说,默认复制级别可以根据需求进行调整.
Master负责维护所有文件系统元数据信息,并将其划分为四个主要部分:命名空间域、访问权限配置、文件至块映射关系以及当前块的位置信息。此外,Master还具备系统级的操作权谋,具体包括:在BDB中涉及的块租赁管理(注释者指出BDB也对此有描述),存储孤岛块时执行垃圾回收,以及将现有块转移至Chunkserver的过程。通过定期发送心跳信号包与每个Chunkserver交互,并接收其运行状态反馈以维持系统的正常运作
将GFS客户端代码链接到每个应用程序中实现了文件系统API,并负责应用程序与Master节点及ChunkServer进行通讯,并执行数据读写操作。然而,在涉及数据传输的操作中,则全部直接通过ChunkServer完成。值得注意的是我们不支持基于POSIX标准的数据接口因此无需在Linux vnode层面部署相关钩子程序
无论是在客户端还是在Chunkserver上进行操作时,并不需要存储文件副本。由于大多数程序在处理大型文件时采用流式处理方式,并且这些程序通常会一次性读取并保持一个巨大的文件对象;或者工作集规模过大导致无法有效管理缓存。通过优化设计减少了对缓存资源的依赖从而简化了客户端与整个系统的配置流程。(然而,在某些特殊情况下客户端可能会维护元数据用于后续分析功能。)同样地,在Chunkserver架构中,并没有存储完整的文件副本;相反每个数据块都会被存储为独立的本地文件。A Linux缓冲区(即内存区域)已经将频繁访问的数据保留在内存中以减少磁盘I/O开销这一特性使得该架构能够高效管理大规模分布式计算任务。
2.4 单一Master节点
单一节点的 master 极大地简化了我们的设计,并且让 master 能够运用全局信息(knowledge)来进行复杂的块部署以及副本决策。然而,在这种情况下我们必须要确保 master 与读写操作的相关性降至最低水平——这样能避免其成为系统瓶颈。此外客户绝不会通过 master 来读写文件数据——相反地,在这种情况下当客户询问时 master 应该指引他们联系哪个 chunk server。这些相关信息会暂时存储在缓存中并随后直接与 chunk server 进行交互。
通过图1展示了一次简单的读取交互过程。具体而言,在这一过程中:客户端通过固定大小的分块机制将应用程序指定的文件名与字节偏移值转化为文件内部对应的分块索引值;随后将包含文件名与分块索引的信息发送至 master 节点;接着 master 节点回复相应的分块句柄以及副本位置信息;最后客户端利用文件名与 Chunk 索引作为键来缓存这些相关信息。
随后客户端向其中一副本提交请求,默认选择最近的那个副本。 请求数据中包含了Chunk标识符以及该块内的字节数量。 对于同一个区块的数据读取操作,在此阶段无需客户端与 master之间的任何交互。 当缓存机制失效或文件重新加载时,“知道”应改为“当”。 实际上,在单个请求数量上可能会包含多个区块;而服务器 master 在响应时会立即包含后续所需的区块数据;这些补充信息在实际应用中并不会增加任何负担;从而避免了后续可能出现的各种通信需求。
2.5 Chunk尺寸
核心参数之一是块的大小设置。我们决定采用64MB作为设置值,在常规文件系统中这一数值明显大于其标准设置值。
每个副本数据按照普通Linux文件格式存储于Chunkserver上,在必要时才会进行扩展。
通过抑制内部碎片化来最大限度地减少空间浪费,
可能成为这一如此之大尺寸所面临的最大争议。
大尺寸块具有一些显著的优势。其次,在某些方面它减少了客户端与master之间的交互需求。具体而言,在某些情况下它减少了客户端与master之间的交互需求。此外,在某些方面它降低了工作负载的压力程度。具体来说,在某些情况下它降低了工作负载的压力程度。此外,在某些方面它降低了存储在master上元数据所需的空间大小。这意味着我们可以将这些元数据保留在内存中进行处理,并且这种方法还带来了其他好处,请参见2.6.1节中的详细讨论
反过来说,在配合惰性空间分配的情况下
然而,在批处理队列系统首次应用于GFS时,问题逐渐显现:通过将单个可执行文件存储为单一数据块的方式会导致多个服务器因并发请求而超负荷运转。其相关服务器群因承受来自数千并发请求的压力而陷入高负载状态。为此我们采取了增大复制因子以存储此类特殊文件,并将批处理队列系统的启动时间和任务调度进行错开优化的方法从而解决了这一技术难点。一种潜在的长期解决方案是在这种情况下引入基于P2P的技术以实现资源的有效共享和扩展能力提升
2.6 元数据
物理主服务器(即物理主节点)负责存储三类关键信息:文件及块的身份标识符(即文件名和k值),文件与特定块之间的对应关系(即元数据中的映射关系),各副本的具体位置信息。这些关键元数据均被保存在物理主节点内存区域中。前两类元数据(身份标识符与映射关系)则会通过操作日志记录其变更历史,并存入该节点本地硬盘,并对外备份以防不测。这种机制确保了系统状态的一致性更新而不必担心主节点失效导致的数据丢失风险。而第三类元数据(即各副本的具体位置信息)则不会被长期保存于物理主节点内存区域中。相反,在物理主节点启动运行或新加入集群的chunk服务器请求时,该物理主节点会发起查询以获取各相关block的信息。
2.6.1 内存中的数据结构
由于元数据存储于内存中,在线 master 操作表现出色。此外,在线 master 定期执行检查任务以确保系统的高效运作。这种定期检查的任务负责管理块垃圾收集过程、确保 Chunkserver 失效后立即进行备份以及均衡跨 Chunkserver 的负载与磁盘使用状况。将在第 4.3 和 4.4 节进一步探讨这些机制。
该方法存在一个潜在担忧即其系统容量受制于master设备内存容量。这一限制在实际应用场景中的影响程度并不显著。具体而言Master设备为每个包含64MB的数据块维护对应的元数据空间需求。通常情况下多数文件包含多个这样的数据分段(即block)因此这些block的空间通常处于满载状态只有最后一个block的空间仅需部分填充即可满足需求。此外每个文件所需的命名空间信息通常不超过64字节并且采用前缀压缩技术以高效存储名称信息从而保证了资源利用效率的最大化
如果需要考虑更大规模的文件系统架构,则与其相比,在master节点上增加额外内存不仅不会显著增加成本(相对于通过内存存储元数据所获得的紧凑性、稳定性以及高效性的提升),反而能够提供良好的扩展性。
2.6.2 Chunk 位置信息
Master不会维护哪个ChunkServer拥有指定块的副本的持久化记录。在启动时仅进行查询以获取相关信息。Master可以保持自己是最新的(thereafter),因为其负责管理所有块的部署,并通过定期的心跳信息来监控ChunkServer的状态。
最初我们尝试将块的位置信息持久存储于master节点,并权衡后发现定期从ChunkServer获取数据并进行查询更为便捷。这种方法排除了包括Node加入/退出/重命名/失效/重启等情况下 master 与 ChunkServer 保持同步的问题,在包含数百个节点的环境中此类问题较为常见
理解和这一设计决策相联系的一种方法就是意识到:只有当Block位于其所在的磁盘上时...
2.6.3 操作日志
该操作日志记录了关键元数据的历史变更信息。(注:文件和块连同其版本的信息都由创建时使用的逻辑时间信息来唯一标识)这对GFS而言具有重要意义。不仅因为它是唯一的持久化记录,
同时也用于定义同步操作顺序的关键依据。
此外,
由于每个文件或块都有一个独特的、基于创建时间点的一致标识符
(alex注:即通过使用文件或对象创建时的时间戳来确定其存在期间),因此这种机制能够保证所有相关事件的时间线可以被可靠地重建。
操作日志的重要性不容忽视, 因此必须对其实施可靠的存储方案, 仅当元数据发生变化并被持久化时, 这种变化才会对客户可见. 如果出现异常情况, 即使块本身没有问题, 我们(实际效果上)确实会导致整个文件系统或最近的操作丢失. 因此, Master节点会在执行写入操作前, 分阶段处理相应的日志记录, 以确保不会因 writes 和 backups 而影响整体系统的吞吐量.
Master通过回放操作日志来恢复其文件系统的状态。为了缩短启动时间的需要,操作日志的大小必须保持较小(注释:即该方法中的操作日志总量应尽量减少)。每当操作日志达到预定大小时,Master会对系统状态进行一次检查点。(注释:此处的"CheckPoint"是一种行为模式,在数据库管理中指定期望对当前的状态进行一次快照记录的操作)这样做的结果是,在每次检查点之后仅需从本地磁盘加载最新的检查点数据,并在此基础上重演有限数量的操作日志记录即可完成系统恢复工作。(注释:该方法是一种高效的技术方案)其中使用的CheckPoint格式是一种类B-树结构(注释:一种优化设计的索引结构),它能够直接映射到内存中以供使用(注释:无需额外解析过程即可完成命名空间查询)。这一设计进一步提升了恢复效率的同时也增强了系统的可靠性和可用性
由于创建一个检查点所需的时间并不短,在处理完成后系统会切换到一个新的日志文件并生成新的检查点。这种结构设计允许在切换日志文件并生成新检查点时不中断后续变更操作。因此,在master节点中,系统的内部状态被组织成这样的形式:一种能够支持快速复制和自动恢复的模式。为了实现这一目标,在处理完成后系统会切换到一个新的日志文件并生成新的检查点。在这种情况下新生成的.checkpoint会包含所有切换前的变更操作记录,并将这些信息保留在本地存储和远程备份中以供后续恢复使用。
Master仅需最新的检查点以及后续阶段的日志记录。旧版本的检查点与日志信息能够手动移除。为了避免出现不可预见的大规模数据丢失(alex注:catastrophes),我们仍然会定期备份这些信息。在创建过程中出现失误并不会影响整体功能。系统会自动修复并省略未完成的部分。
2.7 一致性模型
GFS采用了较为宽松的一致性模型,该模型有效支持了我们高度分布的应用,然而尽管如此,该模型依然相当简单且易于高效实现。我们现在重点探讨了该系统的一致性保障机制及其在应用开发中的重要性,同时详细阐述了其如何维持这些一致性保障,具体实施细节将在后续章节详细说明。
2.7.1 GFS一致性保障机制
文件命名空间的变更行为(如文件创建)具有不可分割性特征。它们仅受master版本控制:命名空间锁确保了这种不可分割性和正确性(4.1节);而master版本的历史操作日志则记录了所有相关操作的一个全局完整且有序的时间线(2.6.3节)。
数据变更后一个文件域的状态由以下因素决定:操作的类型、操作是否成功以及是否存在同步变更。表1汇总了相关结果。如果所有客户端(无论通过哪个副本读取)始终看到完全一致的数据,则认为该文件域是"一致的";在一次数据变更操作成功完成且未受到同步写操作干扰的情况下(暗含该文件域处于一致状态),则认为该领域是"已定义的"(即具有可观察到完整变更内容的能力)。当一次数据变更操作成功执行而没有任何同步写冲突发生时(暗含该状态为一致状态),受影响区域即处于"已定义"状态:所有的客户端都能观察到完整的变更内容。然而,在完成了全部并发变更操作并成功完成后(此时系统处于"一致但未定义"状态),所有客户端将看到相同的数据片段,并且无法反映任何一次实际的变更内容。如果一个数据变更失败(导致系统出现不一致性),则该文件域将处于不一致状态(因此也是未定义的):不同的客户端可能在不同时间点看到不同的数据片段。本系统实现了能够区分已定义和未定义区域的功能描述。
数据变更行为可能涉及两种情况:一是直接存入系统中(即所谓的"写入"),二是新增记录并进行追加操作(即所谓的"记录追加")。具体来说:
- 存储操作会将数据存放在应用程序预设的具体偏移位置。
- 记录追加操作则会保证每次仅将完整的一份数据(即完整的"记录")进行原子化追加。
即使在多个变更操作同时进行的情况下(即GFS 选择的偏移位置),每一次追加操作都会成功完成。(值得注意的是,在常规情况下,在同一个偏移位置上只会执行一次追加操作)。(从另一个角度来说,在传统系统中通常会一次性完成某一个偏移位置上的所有追加操作,并且客户端认为该位置就是文件末尾所在处。)
GFS系统返回给客户端的一个偏移量指标表明:这个指标标识了包含当前所处理的所有已定义域起点所占据的位置。
此外,在某些特殊情况下(例如出现逻辑不一致时),GFS可能会在中间插入填充数据块或复制重复记录块。这些被认定为不一致区域的数据块与实际用户系统中的原始数据相比通常体积较小。
在执行了一系列成功的变更操作后,在被修改的文件域上必定已经预先定义过相关参数,并包含最近一次修改的数据以供后续访问。为了实现这一目标,GFS采用了以下措施:首先,对所有副本采用相同的修改顺序进行更新(见3.1节);其次,利用块版本号来检测过时副本,这些过时副本由于其所属的Chunkserver在经历宕机事件后未能及时提交更新而产生,因此这些副本根本不参与任何修改操作也不会返回给请求块位置信息的客户端;最后,为了避免不必要的资源浪费,这些过时副本将直接被回收以便于垃圾回收系统进行处理
因为客户端存储了缓存块的位置信息,在刷新数据之前可能存在读取失效副本的情况。此时间窗口受限于缓存条目超时时间和文件下次被打开的时间间隔,在这种情况下文件再次打开会从缓存中删除该文件的相关块信息。此外,在我们的系统设计中,默认情况下所有文件仅允许追加操作因此失效副本通常返回的是已终止的数据而非已过期的数据。一旦某Reader重试并与master取得联系它就会立即获取当前的有效块位置信息
经过长时间的成功执行后, 组件失效可能会导致数据毁坏或删除. GFS 通过周期性地与 master 节点及所有 Chunkserver 进行通信过程, 检测到存在失效的 Chunkserver, 并通过校验过程验证数据是否存在毁坏 (参见第 5.2 节). 当问题浮现时, 系统会从有效的副本中快速恢复状态 (参见第 4.3 节). 只有当所有副本均被 GFS 响应并处理之前, 一个块才会出现无法逆转的情况. GFS 的响应时间为几分钟. 即使在此情况下, 块的状态也会变为不可用, 而不会变成毁坏了的数据: 系统将发送清晰的状态错误信息而非毁坏的数据片段.
2.7.2 程序的实现
GFS应用程序遵循一系列基础技术来遵循这一宽松的一致性架构;这些技术能够支持其他需求:避免重写,转而采用追加方式;设置检查点;实施自我验证;存储带有自我标识的数据记录块。
在实际应用中,在我们的系统里所有的应用都采用新增而非覆盖的方式来变更文件。一种典型的场景会生成一个新的临时文档。完成所有数据输入后,在线一次性地将临时文档命名为永久名称,并每隔一定时间记录成功输入的数据量。这些标记不仅包含基本的数据检验还附加了其他层次的检查机制。Readers仅验证并处理上一记录后的字段内容无需考虑一致性与并发问题时这种方法对我们非常适用。新增操作相较于随机 writes 更高效而且在程序出错时更具容错能力这些标记允许 writer 在需要时重新启动而不会影响到读者已经处理过的部分
在另一种典型的场景中。许多Writer为了合并结果或者作为生产者-消费者队列来并发地向一个文件追加数据。每个Writer通过记录追加‘至少一次’来保持输出信息。Reader采用以下方式处理偶尔出现的填充数据和重复内容。每条记录中包含了一些附加信息用于检验其有效性。当遇到异常情况时, Reader会采取措施识别并丢弃多余的内容。如果偶尔出现的数据重复无法容忍(例如, 这些重复的数据可能导致非幂等操作)。这些I/O功能(除了用于去除多余的数据外)都整合到我们共享的应用代码库中, 并且适用于Google内部其他文件接口的功能实现。
3. 系统交互
我们设计这个系统旨在降低master与所有操作之间的牵连。在此背景下,我们将详细阐述客户机、master和Chunkserver之间的相互作用机制, 以实现数据变更、原子记录新增以及快照功能。
3.1 租约(lease)和变更顺序
变种操作包括修改块内容或附加数据的操作,在每个存储单元的全部备份中执行。为了确保这些变化的一致性更新顺序, 我们采用了租约机制来规范这种一致性变化的执行流程。具体而言, Master会对其中一个备份点授予一个特定范围内的数据变化授权, 我们将这个备份点定义为主备份点,并由其负责按照特定序列处理所有相关变化项的变化申请。在实际应用中, 所有相关备份点在处理变化请求时均需遵循此统一的变化执行序列规则, 从而保证数据完整性与一致性得到有效维护
设计租约机制的目的是为了最小化master的管理开销。租约的初始过期时间为60秒。然而,只要块正在变更,主副本就可以请求并且通常会得到master无限期的延长。这些延长请求和批准信息附在master和所有Chunkserver之间的定期交换的心跳消息中。Master有时可能试图在到期前取消租约(例如,当master想令一个在一个重命名的文件上进行的修改失效)。即使master和主副本失去联系,它仍然可以安全地在旧的租约到期后和向另外一个副本授权新的租约。
在图2所示中, 我们遵循控制流程, 并采用标号步骤进行详细地说明
客户机查询 master 中哪个 Chunkserver 拥有该租约,并获取其他副本的位置信息。如果没有任何 Chunkserver 拥有该租约,则会将该租约授权给选定的一个副本(未展示)。
master发送主副本的标识符及其它副本的位置给客户机;客户机会将这些数据存储于未来;仅当主副本不可用或其响应已终止租约时才会再次重连master。
客户机会把信息发送给所有的副本节点。客户机会采用灵活的数据发送序列。当服务器节点的LRU缓存空间不足时,在等待主副本之前先存储到LRU缓存中。通过分离数据传输与控制流程,则无需关注具体哪个Chunkserver拥有主副本,并能优化处理高代价的数据传输。后续章节将在3.2节详细展开讨论。
一旦所有副本均确认接收到数据,则随后客户机向主副本提交写入请求。该请求包含了早前通过所有副本推送的数据信息,并将被用来同步修改后的数据版本。主副本将接收到的所有变更事件依次分配给连续的序列号,并根据这些唯一标识符来组织管理这些变化内容。由于单个客户机可能仅推送部分变更事件,则这种机制能够有效地避免重复处理同一份数据的变化记录。最终系统会按照指定的序列号依次应用这些变更到其本地状态中(例如,在本地环境中执行相应的操作以更新状态)。
主副本负责将请求转发(forward)至所有的次级副本。每个次级副本应按照主副本指定的顺序依次执行变更操作。
6.所有次级副本回复主副本并标明它们已经完成了操作。
主副本将向客户机反馈。所有副本在发生任何错误时都会通知客户机。当出现问题时,在主副本和次级副本的一些子集上可能会顺利完成写操作。(如果主副本出现故障,则不会分配序列号并进行数据转发。)客户端提交的操作被视为失败且涉及字段存在不一致状态。我们的客户端代码会尝试修复这些错误。在此前阶段之前,在回滚至重新开始阶段之前,在退到从头开始重试之前,在步骤(3)至步骤(7)之间会执行几次重试策略。
当应用程序一次提交的数据量较大时,在跨多个块的情况下,GFS客户端代码将请求拆分为多个独立的操作。这些操作均遵循前述控制流程,但可能会因其他客户端同时提交的操作而导致交错或覆盖,从而可能导致共享文件域最终包含自不同客户端提交的数据片段。尽管如此,由于所有副本均按相同顺序执行这些单独的操作,它们仍保持一致的状态.这使得整个存储区域处于第2.7节所述的一致性状态但尚未明确其具体定义
3.2 数据流
为了优化地利用网络资源,在处理过程中我们将数据流与控制流进行分离。在客户机到主副本再到所有次级副本的过程中(即整个传输链条上),数据通过管道传输并依次沿着精心选择的一系列Chunkserver链路进行传输。我们的目标是最大化利用每台机器的网络带宽并避免出现网络瓶颈及高延迟问题,并尽可能减少整体数据传输的时间消耗。
为了最大限度地利用每台机器的带宽资源,在数据传输过程中按照一条连续的Chunkserver链路进行推送,并避免采用其他常见的拓扑结构(如树状架构)。从而确保每台机器的所有出口带宽都被专注于尽可能快速地传输数据,并非分摊给多个接收节点。
为了避免交换机之间的链路成为网络瓶颈的同时保证传输延迟较低(例如通过评估本地可用路径长度),每台设备会根据本地信息自动选择一个最接近且未接收到数据的设备进行通信。当客户端发送请求时,其会选择与之通信距离最短的一方作为目标节点进行交互操作。例如通过评估本地可用路径长度后发现节点A更为合适,则会优先与节点A建立直接连接完成传输任务。依此类推系统会按照预设规则逐步构建起一个高效的数据传输网络架构使得各节点间的通信效率得到显著提升
在最后阶段, 我们采用基于TCP协议的数据管道传输机制来尽量减少延迟. 当Chunkserver接收数据时, 它能够迅速转发. 管道传输机制对我们的工作特别有帮助, 由于我们采用了支持全双工通信的交换网络架构. 即使立即发送数据也不会影响接收速率. 在理想情况下没有网络拥塞时, 传输B字节的数据到R个副本所需的最短时间为(B + R*L)/T, 其中T代表网络的最大吞吐量,L是在两台设备之间传递单份数据所需的时间. 我们的网络连接通常是100Mbps(T), 因此,L远小于1ms. 因此, 在这种情况下每MB的数据大约需要80ms的时间分发出去.
3.3 原子的记录追加
GFS提供了叫做记录追加的一种原子式更新机制,在进行 record append 操作时:客户端仅需提供一个待 append 的数据块即可,并将该数据块原子地添加至主文件,并返回相应的偏移值的位置信息;这类似于 Unix 下以 O_APPEND 方式打开文件时进行并发 file operations 的情形
在我们的分布应用中广泛应用记录的追加操作,在实际应用中我们发现很多情况下不同机器上的客户程序同时对同一文件进行并发追加操作。如果我们采用传统的方式处理这些操作的话客户端将必须面对额外复杂的同步机制例如通过一个分布式锁管理器来实现有效的数据一致性保护。在我们的实际工作中这类文件数据通常被用作多生产者单消费者队列或者将来自多个客户端机的结果整合处理以支持高效的业务流程运行。
根据3.1节所述的控制流程进行变更操作,在主副本中添加了一些额外的控制逻辑以确保数据完整性和一致性。当客户机向系统发送请求时(即发起追加操作),系统会将数据推送给主副本的所有副本,并同步更新相关状态信息。(注:此处记录追加行为受到最大尺寸限制的影响)在这种情况下(即当追加记录可能导致块超出最大尺寸限制64MB时),系统会优先执行快填策略至最大容量上限(即不超过当前块大小的四分之一)。随后会通知次级副本继续执行相同的操作流程。(注:此限制有助于维持最坏情况下数据碎片数量在可管理范围内)如果当前空间足够容纳新增记录(即未触发快填条件),则系统会直接将数据追加至主副本对应的位置并通知次级副本完成相应位置的数据迁移工作。(注:这种设计保证了大部分情况下操作能够顺利完成)最后系统会将成功反馈给客户端并确认操作已完成
当尝试将记录添加到任何副本失败时(即客户端重试操作),可能会导致同一个块的不同副本中存在差异的数据(即可能包含完整记录或部分重复记录的内容)。GFS并不保证所有副本在字节级别上完全一致(即它只确保每个数据单元至少被完整地写入到一个块的所有副本中)。这一特性可以通过简单的观察推断出来:如果操作返回成功,则表示该操作已经将数据正确地写入到了某些块的所有副本中的相同偏移位置,并且这些副本自此之后都会与最后一个记录尾部具有相同的长度(即后续的新记录会被分配到更高的偏移位置或者不同的块中)。因此,在这种情况下(即我们讨论的一致性保障机制),只有那些已经被成功覆盖的数据域才是一致且有定义的;而中间区域由于尚未被正确覆盖而处于未定义状态。
3.4 快照
本章节较为复杂,在此系统地阐述了快照的概念、所采用的COW技术及其具体应用细节,并重点说明了其对当前操作的影响范围。
快照操作近乎即时完成对文件或目录树(标记为"源")的复制过程,并最大限度地减少可能产生的副作用。我们的用户迅速生成一个大数据集的分支副本(通常会反复复制这一过程),或在进行修改测试前进行校验点记录。这样一来,在提交或回滚时会更加便捷。
采用类似于AFS(Andrew File System)的技术架构,在我们系统中实现了基于复制-on-write的标准快照机制。当 master 接收到快照请求时,它会拒绝当前正在被快照保护的所有块中的共享段(即未释放的共享段),以确保后续对这些块进行修改的操作必须与 master 协调以确定共享段的所有者。这使得 master 能够优先创建这些块的拷贝。
当租约被取消或到期时,master将这个操作并以日志的形式记录到硬盘上。接着,master通过复制源文件或目录树的元数据,并将这些日志记录体现在内存状态中。新创建的快照文件与源文件保持一致。
在快照操作完成后,在客户机初次试图将数据写入块C时
4. Master节点的操作
所有的命名空间操作都由Master执行。另外一种方式是Master负责整个系统的块副本管理:首先制定部署策略并创建新的Block副本;其次协调各种系统级活动确保所有Block副本得到全面备份,并在Chunkserver之间均衡负载分配;最后回收闲置的存储空间资源。在本节中我们将逐一探讨这些关键点。
4.1 命名空间管理和锁
大量master操作通常需要较长的时间:例如,在进行快照操作时必须取消被快照覆盖的所有块上的Chunkserver租约。为了避免这些快照操作在运行时影响其他master的操作,在每个name-space中最多允许多个活跃的操作,并通过名称空间域上的锁来保证正确的串行化。
不同于大多数传统的文件系统,GFS不具备详细列出每个目录下所有文件信息的能力.同样无法为同一文件或目录提供别名(如Unix系统中的硬链接或符号链接).A key feature of GFS is that its namespace is logically represented as a lookup table mapping full paths to metadata entries.通过前缀压缩,这个表可以在内存中高效存储.在namespace树中,每个节点(代表一个绝对文件名或绝对目录名)都与一个读写锁相关联.
在运行之前, 每个 master 操作都将获取一组锁. 一般情况下, 如果该操作涉及的路径为 /d1/d2,…,dn 的形式, 那么它将分别在这些目录上实现读锁. 最后一个部分位于全路径 /d1/d2,…,dn 的叶节点处, 这里可能会实现读 lock 或 write 权限.
当我们演示快照操作将当前目录下的文件(/home/user)复制到(save/target)/save/user/目录时
该锁机制的一个显著优点是可以对同一目录进行并发修改。例如,在同一个目录下可同时生成多个文件:每个文件将获得对该目录读取权限以及对该文件名称的写入权限。该目录名的读取锁定能有效防止删除、更改名称及快照操作;该文件名的写入锁定则采用顺序方式尝试用同一名称生成多个文件。
由于名称空间内可容纳多个节点,在这种情况下读写锁对象会采用惰性分配机制进行资源分配。此外,在保持一致性全局顺序的前提下避免死锁的方式是:首先按照层次结构对名称空间树进行排序,在同一层中则按照字母顺序排列。
4.2 副本的部署
该集群采用多层次架构而非单层结构。其系统通常部署在多个机柜上,并运行数百个ChunkServer实例。每个ChunkServer实例可被多个客户机访问。不同服务器之间的通信可能跨越一个或多个网络交换器。此外,在进出至同一个物理机柜时所使用的带宽往往小于该物理环境内所有服务器的总带宽。这种架构在提升分布式存储系统的扩展能力、稳定性和高可用性方面面临着独特的挑战
该部署策略旨在实现两大关键目标:最大限度地提升数据可靠性和确保系统的高可用性;同时优化网络带宽利用率以提高处理效率。仅仅依靠将副本分布在不同服务器上是不够完善的方案;这种策略只能防止单个硬件故障对系统的影响,并充分利用单台服务器的最大处理能力;但无法应对大规模基础设施中断的风险(例如交换机故障、电源问题等)。因此,在这种情况下还需要实施跨机柜的复制策略;这样即使整个服务器集群发生故障或者部分基础设施中断(例如交换机故障、电源问题等),系统也能通过保留部分副本确保数据不丢失。此外,在读取操作方面通常需要经过多个存储单元以提高带宽利用率;然而,在写入操作时由于系统架构限制往往需要经过多个存储单元以避免性能瓶颈;这种权衡利弊后的设计决策是为了平衡系统的性能与可靠性之间的关系。
4.3 创建,重新备份,重新平衡负载
创建块副本的三个起因:块创建,重新备份和重新平衡负载。
每当Master生成一个新块时,系统会自动决定如何放置初始空副本的位置.具体来说,该系统会综合考虑以下几个关键因素:(1)为了提高存储资源利用率,我们会优先将新副本部署至当前平均使用率较低的Chunkserver服务器上.这种做法旨在让各Chunkserver之间的磁盘使用率趋于均衡.(2)为了限制每个Chunkserver服务器在同一时间段内频繁地进行新块生成,尽管每次生成新块的成本较低,但这种策略可能会导致后续大量的并发写操作.由于我们的存储策略是在实际需要进行writes时才会生成新的block结构,因此在我们的"延遲新增與即时讀取"管理模式中,一旦完成数据write完成后会立即切换至不可编辑状态.(3)基于此考虑,我们在规划block复制品的位置时会选择尽可能将其分散到不同的机房区域中以提高系统的容错性和扩展性.
当有效副本数量降至用户设定的目标以下时, master将对其进行重新备份。这可能是由于多个因素造成的:一是Chunkserver不可用,二是Chunkserver报告其副本可能损坏,三是其中一个硬盘因故障而失效,四是副本数下限值可能提高。对于需要重新备份的块,将根据某些特定因素进行优先处理:首先是该块与其副本数下限值之差;例如,相比仅丢失一个副本的情况,丢失两个副本的块应给予更高的处理优先级;其次是更倾向于备份活跃文件而非最近被删除的文件(参见4.4节)。此外,为了最小化对正在运行的应用程序失效所造成的影响,任何因阻塞客户进程而被选中的块都将获得更高的处理优先级。
Master挑选优先顺序最高的数据块并立即指令特定Chunkserver执行复制操作以完成该块在现有有效副本上的复制过程实现克隆。新副本设置目标包括均衡存储空间使用率、限定单个Chunkserver当前进行中的克隆数量以及在机柜间分布副本以确保数据冗余和可靠性。为防止此次克隆导致网络流量显著高于客户端负载Master采取措施控制集群内所有运行中的克隆数量同时各Chunkserver也被配置以管理来自源Chunkserver的数据读请求从而限制其参与当前克隆操作所需的带宽资源。
最后,在周期性地重排复制器(master):它会评估当前的状态,并将这些复制器转移至更适合存储空间优化及负载均衡的位置。与此同时,在这一过程中 master 会逐步填满一个新的 Chunksorter 资源以避免一次性加载大量新块导致拥塞。其部署标准与之前所讨论的一致,并在此决策中 master 还需考虑哪些现有的复制器需要被删除。通常情况下, master 会选择从那些空闲空间利用率低于平均水平(即未充分利用)且对应的 Chunksorter 上存在的复制器进行移除。
4.4 垃圾回收
GFS会在文件删除后延迟立即回收物理空间。
仅限于常规的文件及块级垃圾回收期间懒惰地进行。
我们发现这一方法使得系统更加简单且可靠。
4.4.1 机制
当应用程序删除某个文件时, master象对待其他变更一样, 立即记录了删除操作.然而, 在立即回收资源的同时, 文件将被重新命名为带有删除时间戳的隐藏名称.在master对命名空间进行常规扫描期间, 它会清除所有这些超过三天存档期的隐藏文件(其中设置了可调节的时间阈值).在此之前, 文件仍可通过使用新的特殊名称访问, 或者通过重命名到普通名称的方式恢复.当这些隐藏文件从命名空间中被移除后, 它们的内存元数据会被清除.这样就断绝了它们与所有块的连接.
当类似系统的Block Naming Space进行常规扫描时, Master能够发现那些无法到达任何文件的孤立Block, 并将其元数据从系统中删除. 每当Master与每个Chunkserver交换心跳信息时, 每个Chunkserver会告知Master它所拥有的Block子集. Master将响应所有尚未包含在其元数据中的Block. 这些副本可以在任何时候被删除.
4.4.2 讨论
尽管分布式垃圾回收在编程语言环境中是一个需要复杂解决方案的问题,在GFS中这一过程相对简单。我们能够轻松地辨识出所有的引用信息:这些信息均记录于master独有的专用文件——块映射表中。我们同样能够方便地辨识出所有的拷贝信息:这些拷贝位于每个Chunkserver指定目录下的特定Linux文件中。因此,在这种情况下无法被master系统辨识出的所有拷贝都被视为‘垃圾’。
与空间回收相比,在面对必须强制删除的情况下(即当组件失效成为常态时),垃圾回收方法带来了一些优越性。首先,在大规模分布式系统中,默认情况下组件会失效的情况极为常见;这种情况下该方法既简单又可靠。其次 块创建可能会成功地完成于某些Chunkserver上 而是在另一些Chunkserver上则会失败 master不得不记得重新发送这些失败的删除消息 无论是针对自身的还是针对其他Chunkserver的消息 因此 master不仅能够实现对自身数据的有效管理 还能够在一定程度上减少网络开销 同时保证系统的稳定运行。此外 这种操作不仅实现了批量执行的同时还实现了成本的分摊 并且只会发生在master当前处于相对空闲状态的时候 这样 master 就能够更快地响应那些需要及时关注的客户机请求 最终形成了一个更加高效稳定的系统框架 第三 延缓空间回收作为一种机制 使得意外、不可逆的删除操作得到了一定的缓冲 从而减少了数据丢失的可能性
根据我们的经验,在处理存储优化问题时发现有几个关键挑战。有时会导致用户在面对存储资源紧张情况时难以及时进行微调操作。此外,在某些情况下重复创建或删除临时文件的应用程序无法迅速释放已被占用的空间。为了提高效率,在一个已删除的文件又被明确从系统中删除之前我们采取了加速空间回收的方法来解决这些问题。我们的策略允许用户根据不同需求设置不同的备份与回收策略例如可以选择性地避免备份某些特定目录树中的数据块从而确保数据安全与恢复速度之间的平衡。
4.5 过期副本检测
当Chunkserver出现故障或未能及时处理其失效期间产生的对数据块变更时(也就是漏掉这些变更),可能导致的数据块副本可能被错误地标记为已过时的副本。为此,在每一个数据块中都需要有一个机制来区分哪些是当前的有效副本以及哪些是已经不再有效的过时副本。为此目的,在每个数据块上都需要有一个由master服务器维护的版本号标识符来明确地区分不同的版本信息。
当系统在一个块授予一个新的租约时,它将提升该块的版本编号,随后通知所有相关的最新副本,而这些副本也将更新其持久化状态中的最新编号信息。这一操作会在任何客户端尚未收到通知之前执行,因此也会在对数据块进行实际修改之前完成。如果当前可用的副本数量不足以满足需求,则不会触发版本号的提升操作。当系统重启时,如果发现某个Chunkserver拥有过期的有效信息,主机会自动将其视为失效状态并采取相应措施以维护系统的稳定性
Master通过常规垃圾收集机制有效地清除过期副本;然而,在处理客户机关于块信息的请求时,默认认为过期副本并不存在;为此采取了另一种保护措施:即在通知客户机哪个Chunkserver持有某个特定块的租约关系、或是在克隆操作中要求一个Chunkserver从另一个获取所需数据时(这里指的是同一个逻辑片段),Master都会记录该数据段落的具体版本信息以确保一致性检查的有效性;当客户机或Chunkserver执行相关操作时会自动验证这些版本编号的存在,并因此始终能够获取最新的可用数据.
5. 容错和诊断
我们的系统设计面临的主要难题是如何应对定期出现的组件故障
5.1 高可用性
在GFS集群中的数百台服务器中,在任何特定时间段都会有部分服务器可能出现故障或停运情况。我们采用两套简洁但可靠的方法来确保系统始终处于高可用状态:快速恢复机制以及数据备份策略。
5.1.1 快速恢复
无论Master或ChunkServer在什么情况下停止(无论是正常还是异常),它们均经过精心设计,在短时间内即可恢复状态并完成重新启动过程。实际上,在处理正常或异常终止时,并不区别对待;服务器通常采用直接杀死进程的方式进行常规关闭。当未解决的问题长时间得不到处理而超时后,在尝试重启服务器并再次尝试连接的过程中(或重试)时(或者说是重连之后再进行尝试),客户端和其他服务会感知到系统短暂波动的存在。第6.6.2节详细记录了观察到的服务启动时间。
5.1.2 块备份
如前所述,在不同的机柜中设置了特定的位置以存储各个数据块。
5.1.3 master备份
为了确保数据可靠性 Master 节点会定期备份其状态信息。每个 Master 节点的日志和检查点都会被复制到多台服务器上以确保数据一致性。只有在所有主备节点的日志记录已写入本地硬盘并完成备份后才会提交状态变更。换句话说,在整个系统的管理中, 每个 Master 进程都负责协调所有的变更操作, 并管理如垃圾回收这样的后台任务. 当它出现故障或需要停机维护时, 该 Master 进程通常可以在很短时间内重新启动. 如果主备节点发生故障时(如机器或硬盘问题), GFS 监控系统会利用主备节点的操作日志信息, 在其他可用服务器上自动启动一个新的 Master 节点.
此外,在主 master 发生故障的情况下,“影子” master 依然能够提供文件系统的只读访问功能。这些 “影子” master 不是镜像存储而是附加在主 master 上的功能模块因此它们可能会略微增加响应时间但通常不超过 1 秒。对于那些变更频率较低的文件或对短暂过期结果无显著影响的应用程序而言,“影子” master 可以有效提升读取性能。实际上由于这些 master 的内容直接来自 Chunkserver 服务器应用程序不会感知到任何过期现象而在这一短暂的时间窗口内过期的可能性主要集中在文件元数据如目录信息或访问权限等方面
作为维护自身最新状态的“影子” master系统会定期提取不断增长的操作日志副本并严格遵循与主 master 相同的数据结构组织方式及修改顺序。如同主 master 在启动过程中也会执行 Chunkserver 的轮训操作 但此类操作极少发生 轮训的目的在于识别块副本并获取其交互握手信息来维持状态监控。只有当‘影子’ master 通过主 master 的决策来创建或删除这些副本时才会更新其位置信息
5.2 数据完整性
每个Chunkserver均采用检定和的方法来检测存储数据是否出现损坏。考虑到一个GFS集群通常由数百台服务器支撑着数千块硬盘[shijin注:这些硬盘在日常读写操作中偶尔会出现故障或丢失的情况(第7节详细解释了原因之一)]。我们可以通过其他块备份来恢复损坏的数据[shijin注:然而,在各个Chunkserver之间进行备份对比以检测数据损伤并不现实]。值得注意的是,在GFS架构中进行的一些变更操作(如原子记录追加),并不意味着所有副本都是完全相同的复制[shijin注:因此每个Chunkserver都需要持续维护检定和的方法以确保其本地备份的数据完整性]。
将每个大块划分为64KB的小块单元,并对每个单元分配一个32位的校验字段。这些校验字段不仅与其它辅助信息一样,在内存中存储的同时也会永久记录在日志文件中以供查询,并与其他用户的数据保持独立性。
对于读操作而言,在向请求者传送任意数据之前(不论请求者是客户端还是其他Chunkserver),Chunkserver会验证与读取范围重叠的数据块的校验和。因此Chunkserver不会将损坏传播至其他机器。如果一个数据块与记录的校验和不符,则Chunkserver会发送给请求者一个错误信息,并报告master这一失配情况。作为反应,在这种情况下,请�oriculator会从其他副本中读取数据(同时),而master也会从其他副本中复制该数据块(即)。当一个新的有效副本到位后(即 master命令报告失配的Chunkserver删除该副本)
鉴于此,验证检验和对读操作的性能带来的影响有限。考虑到大多数读操作分散在至少几个数据块中,在这种情况下,在进行验证时我们只需检查并确认少量额外的数据即可完成。此外,在GFS客户端代码中实施的方法是无需任何I/O操作即可完成数据查找与对比过程,并且计算过程可与输入输出操作并行处理。
为了提高性能,在完成对尾部添加操作(即替代现有数据)的过程中实施了针对校验值计算的优化工作。这一措施占据了总工作量的主要比重。我们仅限于对最后一个构建段落依次增加其校验值,并针对新增段落校验值重新计算校验值。即便未被发现存在损坏的情况,在后续读取该段时若发现新校验值与实际存储数据不符,则损坏将会被及时识别出来。
相反地,在进行写操作时若已修改了现有范围,则必须执行以下步骤:首先读取并核实修改范围内首尾两个区块的数据;接着完成写入操作;最后计算并记录新的校验值。若在部分修改前未核实首尾区块的状态,则可能出现新校验值在未被修改区域掩盖潜在问题的情况。
当处于空闲状态时,Chunkserver能够对非活跃区块进行检查和验证其完整性。这种机制有助于发现那些很少被访问的区块可能出现的损坏情况。一旦检测到某区块存在损坏,则 master 会生成一个新的、未受损的副本并删除原有的损害数据。这一过程不仅防止了那些虽然已损坏但仍然非活跃的区块试图欺骗 master 认为其拥有足够的可用区块副本
5.3 诊断工具
详细且全面的诊断日志,在问题隔离、调试、以及性能分析方面发挥着不可替代的作用,并且curring微乎其微的成本
RPC日志记录了精确的write operations所发送的请求和相应的响应,除了存储那些即将读取或写的文件数据之外。通过匹配请求与相应的回应,并汇总来自不同机器上的RPC日志记录,从而重构出完整的消息交互历史以便诊断问题。同时用于追踪负载测试结果和性能指标分析。
日志的作用对性能影响较小(经济效益更为突出),这是因为这些日志采用了连续异步写入的方式。近期发生的事件被存储在内存中,并且可用于持续性的在线监控。
6. 测量
在本节中, 我们进行了系列小规模基准测试, 以深入探讨GFS架构及其实现过程中的固有瓶颈, 并结合Google内部实际应用中的集群数据, 进一步验证了相关特性.
6.1 小规模基准测试
我们对一个GFS集群进行了性能评估。其中包含了一台主节点 master 以及两台备用 master 备份。该集群还搭配了十六个客户端节点(即 Chunkserver 和客户机)。值得注意的是,在测试过程中采用了此配置设置。通常情况下,在实际应用中 GFS 集群将部署数百至上千个客户端节点。
所有机器均配备双核Intel Xeon PIII处理器(型号 unspecified),运行频率达到1.4GHz,并拥有2GB内存容量;此外还安装了两组80GB 5400rpm机械硬盘,并通过一条高速度、大带宽的链路将这些存储设备与一台性能强劲且稳定性卓越的网络设备相互联通。
在存储资源管理方面,则采用了两套独立且高度可扩展的设计方案:
首先,在一台高性能服务器上部署了全部19台General File System(GFS)服务器;
其次,在另一台专用型存储设备上部署了全部16台客户端。
这两个专用型存储设备之间通过一条高速度、大带宽的链路进行通信。
6.1.1 读取
多个客户机同时从文件系统中读取数据。每个客户端从中选4MB的数据块进行处理。经过256次循环后,每个客户端累计处理了1GB的数据量。整个Chunkserver系统的内存资源总计仅达32GB,在这种配置下,在Linux缓冲区中预计命中率达到不超过10%。基于此计算模型,在 Linux 缓冲区中预计命中率达到不超过 10% 的水平,并且这一结果与冷缓存机制下的表现极为相似
图三:合计吞吐量:该图表顶部曲线展示了基于我们网络拓扑结构设定的理想吞吐量上限。底部曲线则反映了实际观测到的吞吐量数值。这些线条带有95%置信度的误差棒标记。值得注意的是,在某些情况下这些误差棒难以识别清楚,这主要是由于测量过程中的数据波动较小所致。
图3(a)展示了N个客户机整体的读取速度及其理论上限。当两个交换机间的1Gbps链路达到饱和时,在这种情况下整体达到了125MB/S的最大值;或者在每个客户机应用100Mbps网络接口达到饱和的情况下,则其速度为12.5MB/s。通过测试发现,在只有一个客户机运行时其读取速率为10MB/s;而当每个客户机都接近其上限速率时,则达到了80%的速度表现。对于16个reader配置下整体读取速率为94MB/s,在这种情况下总效率较80%下降至75%,这主要归因于多个reader同时从同一Chunkserver获取数据的可能性增加所导致的结果
6.1.2 写入
多个客户机同时向各自独立的文件进行数据 write 操作。每台客户机通过连续的 1 MB 块进行数据 writes into 1 GB 总量。如图3(b)所示,在整体 write performance 和理论 upper limit方面进行了详细展示。然而,在达到理论 upper limit 的过程中遇到了瓶颈:为了将每个 byte 数据分配到 3 个独立的 ChunkServers 中进行处理,并且每个 ChunkServer 最多只能处理 12.5 MB/s 的 write 速度。
一个客户端的读取速率达到了6.3MB/s左右,在达到理论极限之前还有较大的提升空间。这一现象主要源于我们的网络协议栈性能与数据分片传输机制之间的不协调性。具体而言,在块级复制过程中所采用的数据传输管道模式存在一定的效率瓶颈。由于这一复制延迟在不同分片间不断累积进而削弱了整体系统的吞吐能力
整体而言,在线情况下有16台客户端机器完成了一次全量数据提交操作(每台机器完成约2.2MB的数据提交),其平均速度仅为理论最大值的一半。与读的情况类似,在线情况下客户端机器数量增加时多台客户端机器同时向同一个Chunkserver提交数据的可能性显著提升。此外,在处理16个writer时出现冲突的可能性较16个reader更高。每次write操作会访问三个独立副本
这项操作的速度低于预期。实际上这并未成为一个主要问题。尽管个别客户机可能感受到延迟增加延时,但对大多数客户机而言,整体上不会对系统已交付的总带宽产生显著影响.
6.1.3 记录追加
图3(c)展示了记录追加性能的表现。多个客户端同时将数据写入同一个文件时展现出良好的扩展性特征。值得注意的是,在此过程中系统主要受到存储在最后一个块中的Chunkserver所使用的网络带宽限制,并因此而实现了不受客户端数量增长影响的高度可扩展性。具体而言,在单个客户端下可达到的最大处理速度可达6.0 MB/s,在增加到16个客户端时,则降至4.8 MB/s以下;这一现象主要是由于网络拥塞以及各客户端之间传输速率差异所导致的结果
我们的系统擅长批量生成多个相关文件。换言之,在实际运行中,有N个客户端同时往M个共享资源中提交数据(其中N和M均为几十或数百)。实际上,在处理大量请求时出现的网络拥堵现象并非显著障碍。
6.2 现实的集群
目前我们考察Google内部所采用的两种集群类型,在结构和功能上与其它相似的集群具有代表性。其中一种(称为集群A)由一百多个工程师广泛应用于研究和开发活动。通常的任务会在人工启动后运行几小时的时间长度,并在其数据读取范围从数十MB扩展至数千TB的过程中进行转换或分析处理,并随后将处理结果写入该集群系统。另一种专门用于处理生产环境下的大量数据(称为集群B),其某些任务所需时间较长,在仅需偶尔干预的情况下继续运行。在以上两种情况下,一个单一的任务可能会占用多个计算节点同时执行,并且这些节点会同时进行多线程的数据读写操作。
6.2.1 存储
如表所示的前五项数据中可以看出,在两个集群中均部署了数百台Chunkserver服务器,并且每台服务器的硬盘容量可支持数TB的数据量。然而这些存储空间尚未完全使用,“已用空间”包括所有块副本。值得注意的是所有文件均被复制并存储了三次副本。因此每个集群分别存储了18TB和52TB的数据总量。
在两个集群中,在A和B两个集群中(原文中的“两个集群有相似……”,此处可能需要更自然地表达),它们所拥有的 file 数量具有一定的相似性。然而,在 B 集群中存在大量已死亡(或已失效)的 file(原文中的“尽管 B 上有大部分……”,这里的表达稍微做了调整)。所谓的‘已死亡(或失效)file’即指那些 file 被删除或者被新版本替代的情况(原文中的“所谓‘死 files’也就是……”,这里做了替换)。需要注意的是,在这种情况下虽然 file 已经不再可用或者被替代了新的 file 生成了新的 version 但在操作系统的磁盘管理机制下这些 file 并没有真正从磁盘上删除而是仍然存在于磁盘上只是不再被系统认为是有效的而已因此即使没有新的 file 替代它们仍然会占用磁盘空间直到相应的磁盘碎片整理任务触发为止(这部分内容比较复杂可能需要进一步简化)。此外 单个这样的 file 由于其较大的 size 它们也占据更多的存储块(原文中的“由于其 files 较大……”,这里做了适当的调整)。
6.2.2 元数据
Chunkserver系统中存储了数以十亿计的数据块(包括用户个人原始信息),这些数据经过检验和处理;仅存于Chunkserver平台上的其他元数据则涉及不同块版本号的相关讨论。
元数据总量较小,在实际应用中通常仅占几十MB左右。换言之(即),平均每份文件约为100字节。值得注意的是(注意:此处为注释起见),这一数值与我们的预期一致。
实际上(master)内存容量并未成为系统资源管理的主要瓶颈。
绝大多数情况下(大部分情况下),大多数(即绝大多数)每个文件中的元数据是以前缀压缩格式存储。
另外一项重要的是(此外),其他类型的元数据主要包括以下内容:
- 文件所有权与许可信息
- 文件到块映射关系
- 每个块段所对应的最新副本位置信息
- 以及针对每个块段所采用的具体复制机制(alex注:即COW引用计数机制)。
每个单独的服务器——无论是Chunkserver还是master——仅存储50MB至100MB的元数据。因此恢复速度非常快:当服务器响应查询时,只需几秒钟即可从硬盘读取这些元数据。然而, master会暂时停滞约30至60秒,直至从所有Chunkserver中获取块位置信息。
6.2.3 读写速率
表格清晰展示了各时间段的数据读写速率。当监测启动时, 这两个集群已持续运行约一周时间。(值得注意的是, 在此之前这些集群均经历过了最新版本GFS的升级, 并进行了相应的重启操作)。
在重启之后,系统的平均写入速率为每秒低于30MB.当我们在进行测量时,B正处于一次 writes 活动中,正在以100 MB/s的速度生成数据.因为 write 操作扩散到三个副本,这一过程导致了每秒 300 MB 的网络负载.
根据预期,在系统设计中占主导地位的操作是大量而非频繁的数据读取操作。实际上,在这两个集群中存在大量的持续性高强度读取任务。特别值得注意的是,集群A在过去的一周内始终保持在580MB/s的稳定读取速率。其网络配置允许最大处理能力达到750MB/s,因此该集群实现了资源的有效利用状态。相比之下,集群B则具备1300MB/s的理论最大值,在实际应用中仅达到了380MB/s的水平。
6.2.4 Master负载
根据表3的数据表明, 发送至 master 操作的速度大约在每秒 200 到 500 个. 相对于该速度而言, master 能够轻松地同步. 由此可见, 该部分的工作负载并非构成系统性能瓶颈.
在GFS的早期版本中, master偶尔会成为某些工作负载的性能瓶颈. 该模块主要耗时于对大型目录(包含数十万文件)进行顺序扫描以定位特定文件. 我们对master的数据结构进行了优化调整,使其能够高效地执行基于名称的空间二分查找. 其性能已显著提升至每秒数千次文件访问水平. 如欲进一步优化性能,则可在命名空间数据结构之前引入名称查询缓存机制.
6.2.5 恢复时间
当一个Chunkserver发生崩溃时,在某些情况下会导致大约15,000个数据块(包含约6,000GB的数据)成为未完全复制的状态(under-replicated)。为了控制对现有服务的影响并为调度机制预留一定的弹性空间(leeway),我们采用了默认策略:允许可行并发备份数量不超过集群总共有91个Chunkserver(占总数量的40%),并且每个备份操作的最大带宽限制定为6.25MB/s(相当于50Mbps)。经过测试,在这种配置下所有损坏的数据块能在约23.2分钟内完成恢复,并且整个过程达到了惊人的440MB/s的速度。
在另一个实验中,我们移除了两个具有约1.6万个块和总计660GB数据的Chunkserver实例。这种双重故障导致266个块数量缩减至仅一个副本的状态;随后,这些266个块均能在2分钟内恢复至至少两个副本的状态;这样做的结果是将集群置于一个能够容忍另一个Chunkserver失效而不发生数据丢失的状态。
6.3 工作负载分解
在本节中,我们对两个GFS集群的工作负载进行了详尽的分解展示,并将其与6.2节中的相关集群进行了对比分析。这些集群在功能上具备相似性但并非完全一致。其中集群X主要用于研究与开发领域,而集群Y则专注于生产环境下的数据处理任务。
6.3.1 方法论和警告
这些结果仅包含客户机发起的所有原始请求数组。由此可见,它们表明我们的应用程序在整个文件系统中产生了相应的负载。然而需要注意的是,这些结果并未涉及任何用于执行客户机请求数组所需的服务器内部操作。同样不包括任何内部后台活动,例如那些被转发的写操作(forwarded\_writes)以及其他诸如负载重新均衡之类的后台操作。
基于启发式重构的信息统计表明,在线IO操作的数据主要来源于GFS服务器记录的实际RPC请求。例如,在GFS客户端代码中将一个读操作分解为多个RPC以提高处理并行度的做法已被广泛采用;通过解析这些RPC我们得以还原出原始的操作序列。由于我们的系统设计具有高度程序化特征,在这种情况下我们预期任何出现的问题都将源自系统噪声(Any observed anomalies are expected to be attributed to noise)。为了保证数据准确性应用程序必须提供详细的记录;然而在实际应用中重新编译及重启数千个运行中的客户端逻辑是不可行的;此外从如此多机器收集结果也将导致耗时较长
必须谨慎避免从我们的工作负载中过度泛化(alex注:即不要将本节的数据作为指导性参考依据)
6.3.2 Chunkserver工作负载
表4:对操作按规模进行拆解(%)。在读操作中,其规模对应于实际处理的数据量而非基于请求的数据量。
表4展示了操作按大小的分布情况。读操作呈现出明显的双峰分布特征,在此特征下可将数据划分为两个不同的区间:一是64KB以下的小规模读取操作主要由密集搜索型客户端完成;二是较大的读取操作(超过512KB)则源于对整个文件进行持续的大片数据读取。
在集群Y上存在大量读操作未返回数据的情况较为常见。我们应用通常将文件作为生产者-消费者队列处理,并且多数情况下会实现多线程环境下生产者的持续追加与消费者的同步读取功能。具体而言,在实际运行过程中可观察到:当多个生产者同时向文件追加数据时(即所谓的并发追加),而消费者则从文件尾部依次进行读取操作;然而,在某些特定情况下可能出现消费者数量超过生产者的状况时无数据反馈的情况;对比之下,在集群X中此类情况较为少见
写作操作的规模呈现出明显的双峰分布特征。较大的写作操作(超过256KB)通常源于Writer内部积累了大量数据进行批量处理。那些在内存中存储了较少的数据量且频繁执行检查点或同步动作,并或直接生成少量数据的Writer负责执行较小规模的小型写作操作(小于64KB)。
观察发现,在集群Y中进行大量记录追加操作的比例显著高于集群X。这是因为我们的生产系统采用了集群Y,并对GFS进行了更具进取心的优化。
表5展示了根据操作规模(百分比)对传输字节进行分解的情况。在读操作中,其大小定义为实际传递的数据量(而非请求总量),若某次读操作超出文件末尾部分进行访问,则两者可能不一致,在我们的工作负载中这种做法是有意为之且较为常见。
表5展示了各个规模的操作的数据传输量。对于各种各样的操作而言,在这些操作中较大的动作(超过256KB)通常承担大部分的数据量传输。较小的读取操作(64KB以下)虽然传递的数据量相对较少但它们在数据读取过程中扮演着关键角色因为涉及随机搜索负载。
6.3.3 追加vs写操作
在我们的生产系统中使用得极为频繁地进行追加操作特别常见。对于集群X而言,在传输的数据量方面追加操作与记录的比例高达108:1,在操作次数方面则为8:1。(作者可能存在笔误?)而对于专门用于我们生产系统的集群Y来说,则分别是3.7:1和2.5:1的比例关系。(疑问:为何会存在这样的差异?)值得注意的是,在集群X上这一比例表明记录操作相较于追加更为常见。然而有趣的是,在测量期间记录操作的整体使用率相对较低这可能导致某些特定的应用程序因选择较小规模缓冲区而产生偏差
不谋而合的是我们的数据变更负载主要表现为记录追加占据而非直接重写的现象。
我们对主副本上发生的重写数据量进行了详细统计。
这一现象类似于客户主动修改已存在的数据而非新增内容的行为。
对于集群X而言,在每次变更操作中平均产生的
****下限仅为千分之零点零零零一。
与之相比,在集群Y中这些指标均达到百分之五。
尽管如此程度虽微小但仍然高于我们的预期。
由此可见其中大部分重复操作源于客户端因错误或超时导致的冗余请求。
这些重复行为并不计入工作负载本身的部分。
6.3.4 Master工作负载
表6展示了对master请求类型的分解。大多数请求主要用于获取读取请求块位置信息(FindLocation),此外还用于获取数据变更请求租约持有者信息(FindLease-Locker)。
在处理集群X与Y的不同数量删减请求时,
由于集群Y存储着生产数据集,
该数据集定期进行更新并由新版本取代。
其中一些特定的操作则隐含地包含于打开请求中,
由于文件的旧版本会在进行擦除操作时隐式地被删除(类似于UNIX系统中的'w'模式定义)。
该 request belongs to a pattern matching type and supports similar operations like 'ls'. This type of requests is more numerous than the requests belonging to master category. In cluster Y, this type of requests is more common because automated data processing tasks typically check a portion of the files system to understand the overall application state. On the other hand, in cluster X, the applications operate under more explicit user control and usually already know all the required file names.
7. 经验
在GFS的建设与部署过程中,我们遭遇了多种多样的挑战。其中一部分源于操作层面的问题,另一部分则来自于技术层面的问题。
最初将GFS设想为我们的生产系统的后端文件系统。伴随着岁月的变迁,该方案逐渐扩展其应用范围,最终用于涉及研究与开发的任务。早期仅对其许可与配额等基础工作有所支持,但如今则涵盖了这些工作的基本形式。尽管生产系统的管理是有条不紊的,然而在实际应用中发现用户往往难以协调工作,因此为了防止这种情况的发生,则需建立相应的基础设施
我们面临的主要挑战是与Linux相关的磁盘问题。有一些磁盘声称它们能够支持从某个特定版本起的IDE协议的Linux驱动程序。然而,在实践中发现它们只能可靠地支持最新版本。由于协议版本极为接近,在大多数情况下都是可用的。然而偶尔出现配置冲突时会引发驱动与内核对驱动状态的不同看法。这种情况可能导致由于内核中的潜在问题而导致数据默默被损坏。这一发现促使我们采用验证机制来监控数据完整性,并且为了彻底解决问题我们的团队决定直接修改内核以解决这些问题。
早期我们在使用Linux 2.2内核时遇到了一些问题。原因之一是fsync()的操作开销与其所处理文件的总大小而非仅限于修改部分的相关大小成正比。这对我们的大型操作日志处理工作带来了挑战,在进行检验之前的阶段我们尝试过同步写技术来解决问题。然而最终解决方案则转移到了基于Linux 2.4内核的设计架构上。
其他和Linux的问题本质上属于单个读写锁问题,在一个地址空间内的任意一个线程试图从磁盘读取页(使用读锁)的时候必须持有该锁。或者在执行mmap()函数以修改地址空间时使用写锁。在轻负载的情况下我们会遇到短暂超时的问题并努力寻找资源瓶颈或者零星硬件错误。最终我们发现,在磁盘线程置换之前映射数据到内存的页时单独使用该锁会导致主网络线程无法将新数据映射到内存中。由于主要受限于网络接口带宽而非内存复制带宽我们可以采用以下措施:将mmap()函数替换成pread()函数以解决这个问题
除了少数例外情况外,在大多数情况下
8. 相关工作
其他类似AFS[5]的大型分布式文件系统中,GFS提供了一个不依赖于位置的命名空间,在这种架构下数据能够实现均衡负载下的透明迁移或容错处理。与AFS不同的是,GFS采用了另一种文件分布策略,其采用了一种类似于Xfs[1]和Swift[3]的方式。这种策略旨在通过提升整体性能并增强容错能力来优化系统设计。
因为磁盘的价格低廉而且操作简单,在相比更为复杂耗时的RAID 9技术下复制性能上更有优势。目前仅采用备份策略来实现冗余从而导致相比于xFS或者Swift而言需要投入更多的原始数据存储成本。
与现有的AFS、xFS、Frangipani[12]以及Intermezzo[6]系统相比,在文件系统层面GFS不提供任何缓存机制。我们的目标工作负载几乎不会在单个应用程序运行期间重复使用。这是因为这些工作负载要么是流式地读取一个大型数据集,在这种情况下它们会一次性读取全部数据;要么是在其中随机搜索,并且每次仅读取少量的数据。
某些分布式文件系统省却了中心服务器,转而依靠分布式算法来确保数据的一致性和可管理性。我们采用中心化的方案,其主要目的是为了简化系统设计、提高运行可靠性以及获得更高的灵活性。特别之处在于,由于master节点拥有大部分关键信息并控制着这些信息的更改,因此它在实现复杂的块部署和备份策略方面具有显著优势。为了维持系统的稳定与高效,我们采取了一种特殊的机制:即在不更新主节点的状态时,将在其他机器上对整个状态进行全复制操作。这种设计不仅能够保障读取操作的扩展性和高可用性,还能通过引入影子master机制来持续优化系统的容错能力。此外,对于master节点状态的变化进行持久化存储的方式也得到了充分考虑:通过向预写日志追加新的日志记录来实现这一目标,从而确保系统的自我修复能力能够持续提升。基于此方案的设计理念,我们有望在未来实现与Harp系统相似但更具增强一致性的高可用性保障机制。
我们在对大量用户群体实现整体性能水平方面实现了与类似Lustre[8]处理相关问题。然而我们通过聚焦于我们的应用需求而非构建一个兼容POSIX的文件系统的特定方法显著降低了复杂性。此外GFS假定了大量的不可靠组件因此错误容错能力是我们的核心设计理念。
GFS与NASD架构具有相似性[4]。尽管NASD架构基于网络附加磁盘驱动器而设计,而GFS则采用普通计算机作为Chunkserver运行(如同其原始形态),但两者的工作原理存在显著差异。具体而言,在工作流程上,我们的Chunkserver采用了惰性分配策略,并固定了块的大小(而非动态分配较长的对象)。另外值得注意的是,在功能实现方面,GFS不仅限于提供基本服务(如重新平衡负载、备份及恢复等),还支持诸如重新平衡负载、备份以及恢复等在生产环境中必要的功能。
而非 Minnesota’s GFS 和 NASD 而言,在存储架构上我们并没有打算更改。我们的目标是利用现有的常规组件满足复杂分布式系统在日常数据处理方面的应用需求。
该系统通过实现生产者-消费者循环链路的方式,在原子记录追加机制下解决了类似于River[2]中的分布式队列问题。其中River采用跨机器分布、基于内存的队列组织方式,并注重数据流的安全控制;而GFS则通过允许多个生产者并发追加记录至持久化文件来提高性能。值得注意的是,在分布式架构方面River模型支持m到n规模的比例配置;但缺乏伴随持久化存储的容错机制这一缺陷;相比之下,GFS系统仅能高效支持单点对多点(m到1)的队列模式。此外,该系统允许多个消费者共享同一份文件,但在实际应用中,为保证性能稳定,必须合理分配负载范围。
9. 结束语
Google filesystem demonstrates the capability to support large-scale data-processing workloads in typical hardware configurations. However, while some design decisions are tailored for specific setups, many decisions can also be applied to similar scale and cost-aware data-processing tasks.
首先,在深入分析当前与预期业务负载和技术环境下对传统文件系统的前提假设后发现:在设计领域中的观察结果表明:将组件失效视为常态而非例外是合理的做法;优化流程通常会先增加(可能同时进行)然后延迟处理那些大文件(通常是按顺序处理)。同时,在改进过程中既扩展了现有接口又放宽了对标准接口的要求以进一步提升系统的整体性能与兼容性
我们的系统采用持续监控机制,并对关键数据进行定期备份,在发生故障时能够迅速且自动化地恢复。块级备份策略允许我们容忍Chunkserver发生故障的情况。这些失效事件的发生频率促使我们开发出一种创新性的在线修复机制。为了实现这一目标, 我们定期进行透明的数据修复操作, 并及时补偿丢失的数据副本。此外, 在磁盘或IDE子系统层次上进行数据损坏检测是我们采用的一种重要方法, 由于系统的限制因素, 在此情形下出现的问题是较为常见的
我们对大量并发执行的各种reader和writer任务实现了极高的吞吐量水平。通过将文件系统的管理与数据传输进行了模块化分离,并由master节点进行集中管控,在传输过程中仅需处理Chunkserver与客户机之间的数据交互。通过优化主副本的数据变更授权机制,并尽量减少大块尺寸和块租约数量的使用,在保证系统性能的同时让一个相对简单的中心化架构不再成为性能瓶颈。我们相信,在现有网络协议栈基础上进行改进将进一步提升各客户端在写入方面的吞吐量限制。
GFS有效地支撑了我们的存储需求,并在Google内部被用作存储平台。无论是在研究与开发领域还是在生产数据处理方面,在WEB范围内都得到了广泛应用。它充当了一个关键工具,在持续推动创新并解决WEB范围内的各种难题上发挥着重要作用。
