本文是GFS论文的学习笔记。GFS(Google File System)是谷歌针对分布式大数据处理而设计的大规模分布式文件系统。在设计GFS的时候主要考虑了应用场景的以下特性:
- 故障时常发生
- 文件十分巨大
- 大部分文件修改操作只是追加而不是覆盖
- 协同设计文件系统和应用可以增加系统的灵活性
系统概览
根据应用场景的特性,论文对文件系统做了以下设定:
- 系统由廉价的硬件构成,因此故障也是时有发生的事情
- 系统需要存储大量大文件
- 读取任务主要包括大数据量的流式读取和小数据量的随机读取
- 写入任务主要是大数据量的数据写入到文件末尾
- 系统需要实现相应功能,保证不同客户端能够高效并行写入同一个文件
- 高带宽比低延迟更重要
GFS以目录树的形式组织文件,但是并没有提供类似POSIX标准的文件系统操作。操作只要包含创建、删除、打开、关闭、读取、写入、快照和记录追加,其中快照操作用于快速复制一个文件或者目录,记录追加功能允许多个客户端并行追加数据到一个文件。

GFS的集群由一个主服务器(master)和多个块服务器(chunkserver)构成。每个文件由文件块组成,文件块采用64位的块句柄进行唯一标识,原文中文件块大小为64MB,不过这是16年前的系统,现在64MB显得就太小了。
- 主服务器:保存了整个文件系统的元数据,元数据包括命名空间、访问控制信息、文件到块的映射和每个文件块的位置,还要负责文件块租借管理、垃圾回收、文件块迁移,在运行过程中通过心跳包和块服务器交换信息。
- 块服务器:保存文件块并向主服务器报告状态。
- 客户端:从主服务器获取元数据,然后再从块服务器读写文件数据。
文件块大小
文中表示,使用较大的文件块好处多多:
- 能够减少客户端向主服务器查询块位置的次数,降低了主服务器负载
- 在单个文件块内的操作变多,降低了网络负载
- 减少了主服务器需要保存的元数据的数量
当然,大文件块会导致小文件只存在一个文件块中,因此如果大量客户端对某个小文件的写入,那么负载会集中在某几台保存这个文件块服务器上,不过可以增加小文件的副本数量。
元数据
主服务器保存的元数据主要为:文件命名空间、文件到数据块的映射和数据块的位置。文件命名空间和文件到数据块的映射的数据需要持久化地写入到一个操作日志中,而数据块的位置通过和块服务器通信获得。
这些元数据需要保存在主服务器的内存中来加速访问。主服务器在运行中会周期性地进行扫描,来完成数据块垃圾回收、故障块服务器数据重备份以及迁移数据块进行负载均衡。
操作日志记录了元数据地修改历史,主要用于故障恢复。显然,操作日志还需要在主服务器之外的远程服务器备份,所有操作必须在写入到操作日志之后执行。操作日志通常会结合检查点机制进行高效地备份。
一致性模型
在分布式文件系统,一个文件被修改之后会出现以下状态
| 写入 | 追加 | |
|---|---|---|
| 顺序成功执行 | 确定 | 确定+不一致 |
| 并行成功执行 | 一致+不确定 | 确定+不一致 |
| 失败 | 不一致 | 不一致 |
- 一致:所有客户端看到的数据块是一样的
- 确定:文件满足一致性,并且每次修改都能知道具体修改的内容
一次成功的独立写入后的数据是一致的,成功的并行写入后的数据是一致,但是并不知道每个修改的具体内容。修改操作包括写入和记录追加,记录追加保证至少一次是确定的(详细见后续解释)。
GFS中文件确定性和实时性有以下两个策略确定:
- 在所有副本上的修改保持一致的顺序
- 使用块版本来识别块是否过期
客户端可以缓存块位置,缓存超时或者重新打开文件后即重新获取位置。GFS主要通过心跳判断块服务器是否正常,如果一个块在所有块服务器上找不到,那么即为丢失,访问的时候返回错误信息。
系统交互
租约和修改顺序
修改操作包括对文件元数据和内容的修改,例如写入和追加。GFS使用租约来保证多个副本之间修改顺序的一致性,主节点会将租约授权给某个副本,每个租约有一定的有效期。
- 客户端询问主服务器持有当前块租约的块服务器以及其他副本位置。如果目前没有租约,那么选择一个副本授权租约。
- 主服务器回复所有副本位置和主副本标识。
- 客户端将数据推送到所有副本上。
- 当所有副本确认收到了数据之后,客户端给主副本发送写入请求。然后,主副本规定顺序执行修改操作。
- 主副本将写入请求发送给其他所有从副本。每个从副本按照相同顺序执行修改操作。
- 从副本告知主副本完成了操作。
- 主副本答复客户端

数据流
为了提高网络效率,数据流和控制流是互相分开的。控制流从客户端开始,到主副本,再到从副本,而数据线性地沿着一个块服务器串以管道的形式传输。块服务器的串从客户端开始,每次选择最近的块服务器作为下个节点。
原子记录追加
GFS提供了原子记录追加功能,将数据追加到文件末尾,并将新数据的起始位置返回给客户端。追加操作写入前,主副本会检查追加数据是否会超出最后一个块的大小。如果超出,那么填补剩余空间,将数据追加到新的块中,并将位置告知客户端。否则,直接将数据追加到最后一个块中。
当任何一个副本写入故障之后,客户端就会开始重试,那些故障时写入的数据区就产生了不一致,因此只能保证数据写入至少有一次是一致的。
快照
快照能够快速复制一个文件或者文件夹树,采用写时复制。
主节点操作
命名空间管理和锁定
GFS支持命名空间上的锁操作来保证串行。GFS维护了一个文件路径到元数据的映射,以前缀压缩的形式保存。每个文件夹或者文件是命名空间树中的一个节点,每个节点都有读写锁。
每个操作之前需要获取一系列的锁。如果某个操作涉及/d1/d2/.../dn/leaf,首先需要获取文件夹/d1,/d1/d2,...,/d1/d2/.../dn上的读锁,然而获取文件上的读锁或者写锁/d1/d2/.../dn。例如,我们将/home/user快照副本到/save/user上,那么可以给/home/user加写锁阻止其他程序在/home/user下创建新的文件。显然,锁需要按顺序获取来避免死锁。
副本存放
副本存放策略需要达到两个目的:
- 最大化数据可靠性和可用性
- 最大化网络带宽利用率
因此,副本需要存放到不同的机架上,读取操作就能利用多个机架的带宽。但是,写入操作的数据流需要跨越多个机架,这是可以接受的权衡。
创建、重新复制和重新平衡
快副本在三种情况下创建:块创建、重新复制和重新平衡。
当块创建的时候,块服务器的选择规则为:
- 选择磁盘利用率低于平均值的服务器
- 限制最近创建块数量
- 将块散布到不同机架上
重新复制会发生在:块服务器失联、副本损坏、磁盘损坏、备份数量增加。块的复制有优先级,一般是距离备份目标数量的差距,以及优先处理正在阻塞程序的块。
重新平衡移动副本位置来平衡磁盘空间和负载,一般将副本从高磁盘利用率的服务器移动到低磁盘利用率的服务器。
垃圾回收
当应用程序要求删除一个文件时,主节点将文件重命名为一个隐藏名并记录时间,三天之后才物理删除。在物理删除之前,还可以使用隐藏文件名读取以及撤销删除。运行期间,主节点会扫描那些无法到达的块并删除元数据,块服务器通过心跳得知不再有用的块。总之,所有主节点不知道的副本都是“垃圾”。
垃圾回收相对立即删除有以下优势:
- 在日常发生组件故障的分布式系统中,垃圾回收比较可靠
- 把空间回收放入主节点后台活动中
- 延迟回收给意外删除提供了回旋的余地
垃圾回收的坏处就是无法在空间紧张的时候快速腾出空间,不过可以修改回收副本和策略解决。
过期副本检测
GFS使用块版本识别副本是否过期,每次授权新的租约之后增加版本号,然后通知其他副本更新版本号。
容错和诊断
高可用
高可用主要通过快速恢复和副本来实现。
- 快速恢复:主节点和块服务器都能够快速恢复和启动。
- 块副本:每个块被复制到不同机架上的不同块服务器上,当然还有其他创建副本的方式,例如类似于RAID3中的奇偶校验。
- 主节点副本:主节点的操作日志和检查点会被备份在多个机器上,日志中包括修改操作已经后台任务。当主节点崩溃之后,GFS使用检查点和操作日志启动新的主节点。另外,可以设置一些影子主节点来缓解主节点压力,影子主节点上的文件元数据会有短时间的过期。
数据完整性
每个块服务器使用校验和来检测数据完整性,每个块服务器负责检测自己维护的数据。在读取数据时,块服务器首先检查数据完整性,如果检查出错则返回错误。在写入数据时候,需要计算更新校验和。在空闲时间,块服务器需要扫描和检查那些不活跃的数据块。
诊断工具
GFS主要采用诊断日志来进行问题排查、调试和性能分析。
参考文献
- Ghemawat, Sanjay, Howard Gobioff, and Shun-Tak Leung. "The Google file system." (2003).