本文为 Google 发表的The Google File System论文阅读笔记,GFS(Google File System)是由Google设计并实现的为大规模分布式数据密集型应用程序设计的可伸缩(scalable)的分布式文件系统。
GFS 概述
GFS假设
- 设备故障是常态:GFS 假设设备故障是常态,因此设计了一套完善的容错机制,包括数据备份、自动恢复等。
- 文件是大文件:GFS 假设文件是大文件,期望是能够存储几百万个大小为100MB左右或更大的文件。系
- 顺序读写:GFS 假设大部分的读写操作是顺序读写,因此设计了一套适合顺序读写的存储管理机制。
- 多用户并发读写:GFS 假设有多个用户并发读写文件,Google的文件通常在生产者-消费者队列中或多路归并中使用。来自不同机器的数百个生产者会并发地向同一个文件追加写入数据。
- 高吞吐率比低延迟更重要:GFS 假设高吞吐率比低延迟更重要,Google的大多数应用程序更重视告诉处理大量数据,而很少有应用程序对单个读写操作有严格的响应时间的需求。
- GFS接口
- 对于客户端而言,GFS 提供了一个类似于 POSIX 的文件系统接口,包括创建、删除、打开、关闭、读、写、定位、重命名等操作。客户端只需要调用这些接口,而不需要关心底层的存储管理机制。另外,GFS 还提供了一些额外的接口,如 snapshot(快照)、record append(追加记录)。
- GFS 架构
- 一个GFS集群由一个
主节点(master)
和多个chunkserver
组成。主节点负责元数据管理,chunkserver负责数据存储。客户端通过主节点获取元数据信息,然后直接与chunkserver通信进行数据读写。

在GFS中,文件被分为固定大小的 chunk
,chunk
是GFS的基本存储单元,每个 chunk
的大小为64MB, chunk
的元数据大小为64bit。chunk
的副本数由用户指定,通常为3个。chunk
的副本分布在不同的 chunkserver
上。
master
负责存储GFS的元数据,元数据包括 文件和chunk的命名空间(namespace)
、文件与chunk的映射关系
(维护了一个文件是由哪些chunk组成的)、chunk的位置
(chunk存在于哪一个chunksever上,这个数据不会持久化)。所有元数据都存储在master的内存中,对于前两种元数据,master会定期地将其持久化到本地磁盘上,并在远程备份。master不会持久化保存哪台chunkserver含有给定的chunk的副本的记录,而是简单地在启动时从chunkserver获取信息。
master还控制系统级活动如chunk 租约(chunk lease)管理
、孤儿chunk垃圾回收
(garbage collection of orphaned chunks)和chunkserver间的 chunk迁移
(migration)。master周期性地通过心跳(HeartBeat)消息与每个chunkserver通信,向其下达指令并采集其状态信息。
在GFS中,chunk
的大小设计为64MB,是一个比较大的值。这样做主要有下面几个优点:
- 减少元数据的数量:相对于小文件,
chunk
的数量会减少很多,从而减少了元数据的数量。 - 减少网络开销:为chunk较大,client更有可能在一个chunk上执行更多的操作,这可以通过与chunkserver保持更长时间的TCP连接来减少网络开销。
- 减少了client与master交互的次数:client在读写文件时,只需要与master交互一次,获取文件的元数据信息,然后直接与chunkserver通信。
但是,chunk
较大也会带来一些问题:
- 内部碎片:如果一个文件的大小不是
chunk
的整数倍,那么最后一个chunk
会有很多内部碎片。 - hot spot:由于一个chunk太大,那么可能对这个
chunk
的访问频率也会很高,这样就会导致hot spot
问题。
- 操作日志
- 包含重要的元数据更改的历史记录,master会将操作日志持久化到本地磁盘上,操作日志还能定义并发操作的顺序。
- 文件区域状态
- GFS定义了三种文件区域状态,文件区域状态可能会在被写入时发生变化,这三种状态分别是:
- consistent:一个文件区域的任意一个副本被任何client读取总能得到相同的数据。
- defined:在一个文件区域的数据变更后,如果它是一致的,且client总能看到其写入的内容。
- inconsistent:文件区域在一个失败的变更后状态会变为inconsistent,不同client可能看到不同的数据。
- consistent but undefined:所有客户端能考到同样的数据,但数据可能并不反映任何一个变更写入的数据。通常,数据融合了多个变更的内容。

GFS能够保证在一系列变更执行成功后,被变更的文件区域状态为defined的 这一点是GFS通过以下方式实现的:
- 对chunk执行变更时,其所有副本按照相同的顺序应用变更;
- 使用chunk版本号(chunk version)来检测因chunkserver宕机而错过了变更的陈旧的chunk副本(章节4.5)。陈旧的chunk副本永远不会在执行变更时被使用,也不会在master返回client请求的chunk的位置时被使用。它们会尽早地被作为垃圾回收。
GFS租约
clint向GFS更改数据只能从租约
节点进行,租约
节点是一个chunkserver,由master授权。primary为应用于该chunk的所有变更选取顺序。所有副本都会按照这个顺序来应用变更。
这种租约机制是为了最小化master管理负载而设计的。租约的初始超时时间为60秒。然而,一旦chunk被变更,primary就可以向master请求延长租约时间,或者(通常为)接受来自master的租约时间延长操作。这些租约延长请求和租约授权请求依赖master与chunkserver间周期性地心跳消息来实现。
GFS写流程

-
client向master询问哪个chunkserver持有指定chunk的租约及该chunk的其他副本的位置。如果没有chunkserver持有租约,那么master会选择一个副本对其授权(这一步在图中没有展示)。
-
master回复primary副本的标识符和其他副本(也称secondary)的位置。client为后续的变更缓存这些信息。client只有当primary不可访问或primary向client回复其不再持有租约时才需要再次与master通信。
-
client将数据推送到所有副本(并没有将数据直接写入文件,而是先写入到一个临时区域)。client可以按任意顺序推送。每个chunkserver都会将数据在内部的LRU中缓存,直到数据被使用或缓存老化失效(age out)。通过将数据流和控制流解耦,我们可以使用基于网络拓扑的技术来提高开销高昂的数据流的性能,且与哪台chunkserver是primary无关。
-
一旦所有副本都确认收到了数据,client会向primary发送一个write请求。这个请求标识了之前推送到所有副本的数据的作用。primary会为其收到的所有的变更(可能来自多个client)分配连续的编号,这一步提供了重要的顺序。primary对在本地按照该顺序应用变更。
-
primary将write请求继续传递给其他secondary副本。每个secondary副本都按照primary分配的顺序来应用变更。
-
所有的secondary副本通知primary其完成了变更操作。
-
primary回复client。任意副本遇到的任何错误都会被报告给client。即使错误发生,write操作可能已经在primary或secondary的任意子集中被成功执行。(如果错误在primary中发生,那么操作将不会被分配顺序,也不会被继续下发到其他副本。)只要错误发生,该请求都会被认为是失败的,且被修改的区域的状态为inconsistent。client中的代码会通过重试失败的变更来处理这种错误。首先它会重试几次步骤(3)到步骤(7),如果还没有成功,再从write请求的初始操作开始重试。
如果应用程序发出的一次write请求过大或跨多个chunk,GFS的client代码会将其拆分成多个write操作。拆分后的write请求都按照上文中的控制流执行,但是可能存在与其他client的并发的请求交叉或被其他client的并发请求覆盖的情况。因此,共享的文件区域最终可能包含来自不同client的片段。但共享的文件区域中的内容最终是相同的,因为每个操作在所有副本上都会以相同的顺序被成功执行。
数据流与控制流解耦
GFS将数据流和控制流解耦,这样可以使用基于网络拓扑的技术来提高开销高昂的数据流的性能,且与哪台chunkserver是primary无关。
在控制流从client向primary再向所有secondary推送的同时,数据流沿着一条精心挑选的chunkserver链以流水线的方式线性推送。Google的目标是充分利用每台机器的网络带宽,避免网络瓶颈和高延迟的链路,并最小化推送完所有数据的时延。
当chunkserver收到一部分数据时,它会立刻开始将数据传递给其他chunkserver。因为GFS使用全双工的交换网络,所以流水线可以大幅减少时延。发送数据不会减少接受数据的速度。如果没有网络拥塞,理论上将$B$个字节传输给$R$个副本所需的时间为$B/T+RL$,其中$T$是网络的吞吐量,$L$是两台机器间的传输时延。
名词解释
- chunk
- GFS的基本存储单元,每个chunk的大小为64MB。chunk的副本数由用户指定,通常为3个。chunk的副本分布在不同的chunkserver上。
- chunkserver
- GFS的存储节点,负责chunk的存储、读写等操作。
- master
- GFS的主节点,负责元数据管理,chunk的分配、副本的管理、chunk的迁移等。
- chunk lease
- chunk租约,master通过租约机制来控制chunk的读写权限。master会向一个chunkserver授予一个chunk的租约,这个chunkserver就可以对这个chunk进行读写操作。
- garbage collection of orphaned chunks
- 孤儿chunk垃圾回收,master会定期扫描文件系统,找出不再被使用的chunk,然后通知chunkserver删除这些孤儿chunk。
- check point
- Master节点会在磁盘中创建一些checkpoint点,这可能要花费几秒甚至一分钟。这样Master节点重启时,会从log中的最近一个checkpoint开始恢复,再逐条执行从Checkpoint开始的log,最后恢复自己的状态。(这样能够避免当Master节点故障重启,并重建它的状态,你不会想要从log的最开始重建状态,因为log的最开始可能是几年之前)
- chunk 版本号
- master使用chunk版本号来检测因chunkserver宕机而错过了变更的陈旧的chunk副本。陈旧的chunk副本永远不会在执行变更时被使用,也不会在master返回client请求的chunk的位置时被使用。它们会尽早地被作为垃圾回收。