写在前面

最近没什么论文好看,于是乎又翻阅起了分布式存储系统的老祖宗GFS。开始愈发理解之前的领导花总说的:常读常新。

之前也阅读过GFS的论文,但总是一带而过,很多问题没有想的特别清楚。于是,趁着这次阅读,将我对于GFS的理解总结下来,希望有所帮助。

再次阅读GFS,给我最大的感触是:许多的问题,只能这么做,而且最好就这么做,也充分体会到了简洁优雅的系统设计给我带来的冲击。

背景

GFS是Google为其内部应用设计的分布式存储系统。Google可能是这个星球上最大的数据工厂了。如何高效可靠地存储如此大规模的数据成为一个很棘手的问题。纵观Google的内部应用,数据访问有以下特点:

  1. 数据集庞大,包括数据总量和单个文件都比较大,如应用常常产生数GB大小的单个文件;
  2. 数据访问特点多为顺序访问,比较常见的场景是数据分析,应用程序会顺序遍历数据文件,产生顺序读行为;
  3. 多客户端并发追加场景很常见,极少有随机写行为;
  4. 文件一旦写入完成,一般接下来都是读访问,基本不会再修改,所谓的一次写入,多次读取,例如互联网上的网页存储。

GFS是Google针对其数据访问模式而设计的分布式存储系统。

特点

GFS具备以下特点:

  • 高度扩展性,单个GFS集群文件系统可扩展至几千个节点,数PB的容量;
  • 高可靠性,GFS运行在普通x86服务器上,但GFS通过多副本等机制保证可靠性;
  • 满足POSIX语义,应用程序无需任何修改即可使用GFS。

架构

GFS系统主要存储文件系统两类数据:

  • 文件元数据:包括file system namespace(目录结构、文件元数据等)、文件存储位置信息等;
  • 文件数据:文件在磁盘上的存储格式

在GFS中,文件被切割为固定大小的Chunk然后分散存储,典型的Chunk大小为64MB。Chunk也是数据复制的基本单位,每个Chunk默认复制三份存放在不同的地方(机架、机器、磁盘)。

Google File System

元数据节点

也称为Master。存储系统元数据信息,主要包括namespace、文件chunk信息以及chunk多副本位置信息。Master是系统的中心节点,所有客户端的元数据访问,如列举目录下文件,获取文件属性等操作都是直接访问Master。除此之外,还承担了系统诸多的管理工作,我们会在后面详细说明。

数据节点

是文件chunk的存储位置。每个数据节点挂载多个磁盘设备并将其格式化为本地文件系统(如XFS)。将客户端写入数据以Chunk为单位存储,存储形式为本地文件。

客户端

提供类POSIX文件接口,应用程序使用客户端与GFS交互。

整个系统的架构特别清晰,功能划分很明确。系统的复杂性在于各种异常处理,而异常又是分布式系统的常态。

Master

Master最主要的两个任务是管理file system Namespace信息和文件的Chunk位置信息。

文件系统元数据

文件系统的元数据主要是文件系统的目录树,在GFS中存储为B+树。树上的每个叶子节点代表普通文件,而中间节点则代表目录文件。根节点是文件系统的根目录。

Master主要存储文件元数据信息,包括:id、文件大小、创建时间、文件的chunk信息等。在论文中说明单个文件的元数据信息不超过64字节。Master会将这些元数据信息进行持久化存储。

Master启动时会将所有元数据加载至内存中,优点是元数据操作速度很快,缺点是限制了文件系统的可扩展性,如64GB内存的服务器最多可支持的文件数量约为 64GB/64B = 10亿。但由于GFS应用场景是大文件,所以这个问题对于他们来说并不严重。

另外,由于元数据的重要性,Master会将文件元数据持久化存储。每一次元数据的更新操作都会先写日志,再应用到内存的B+树,这样即使出现断电等异常,也不会丢失更新。系统重启时,会重放日志在内存中构建B+树。

为了避免日志过大而引发启动缓慢问题,Master会定期进行日志回收,原理是:将当前内存状态冻结并持久化存储到磁盘上,称为Checkpoint;然后,回收该时刻之前的所有日志。若系统此时重启,只需要:1. 将Checkpoint加载入内存;2. 重放Checkpoint点之后的日志即可在内存中重构最新状态。

Chunk管理

大文件会被切割成Chunk(64MB)存储,每个文件至少存储为一个chunk,于是,小文件的chunk大小就会小于64MB。每个Chunk有全局唯一标识符。文件元数据中存储了file id至chunk映射关系。

由于Chunk以多副本方式存储,Master还需要维护Chunk位置信息。Chunk的位置信息ChunkServer周期性汇报,Master只在内存中维护该信息,无需持久化。

核心问题

数据一致性

GFS定义了几种一致性:

defined:状态已定义,从客户端角度来看,客户端完全了解已写入集群的数据,例如,客户端串行写入且成功,此时的状态是defined,如下图1;
consistent:客户端来看chunk多副本的数据完全一致,但不一定defined,如下图2,一般发生在多客户端并发更新时
unconsistent:多副本数据不一致,如下图3;
undefined:数据未定义,如下图4

且论文中写明了各种情况下的状态:

Google File System

下面分别从over-write和append两个场景来阐述:

串行Over-Write

over-write由客户端指定文件更新offset。当客户端是串行更新时,客户端自己知道写入文件范围以及写入数据内容,且本次写入在数据服务器的多副本上均执行成功。因此,本次写结果对于客户端来说就是明确的,且多副本上数据一致,故而结果是defined。如下图:

Google File System

并行Over-Write

并行写入时多个客户端由于写入范围可能交叉而形成交织写。这时候,由于单个客户端无法决定写入顺序(只有主副本才能决定谁先写谁后写),因此,即使写入成功,客户端仍无法确定在并发写入时交叉部分最终写入结果,但是因为写入成功,所以多副本数据必然一致。如下图:

Google File System

图中红色部分代表并发追加的部分,这部分数据由于无法确定谁先谁后执行,因此结果不确定。但由于跟新成功,因此,副本间数据是一致的,这就是consistent but undefined。

无论是穿行还是并行over-write,一旦失败,多个chunk副本上的数据可能都不一致了,其次,客户端从不同的副本上读出的数据也不一样(可能某些副本成功而某些副本失败),因此,必然也是undefined,也是inconsistent。

append

客户端append操作无需指定offset,由chunk主副本根据当前文件大小决定写入offset,在写入成功后将该offset返回给客户端。因此,客户端能够根据offset确切知道写入结果,无论是串行写入还是并发写入,其行为是defined。如下:

Google File System<