本文为我读GFS论文后做的总结,包括文件系统的读写流程,和一些重要机制。
[Google File System]
设计理念
- 组件失效被认为是常态事件。GFS包括数百上千台普通设备,在任何时间都有可能发生某些组件无法工作,因此必须实现错误侦测、灾难冗余和自动恢复。
- 文件大,通常为数GB的文件。因此相比管理大量KB大小的小文件,GFS的I/O操作和block尺寸需要重新考虑。
- 绝大部分文件修改是在文件尾部追加数据,而不是覆盖原有数据。数据的追加操作是性能优化和原子性保证的主要考量因素。
- 应用程序和文件系统API的协同设计提高了整个系统的灵活性,得以简化GFS的设计,例如放松对GFS的一致性要求。
架构
一个GFS集群包含一个单独的Master节点(但可能有多个master服务器作为备份)和多台Chunk服务器。GFS储存的文件被分割成固定大小的Chunk,并为这些Chunk分配唯一的64位标识。每个Chunk都会被复制到多个Chunk服务器上,默认是储存三份。
Master节点管理文件系统元数据,包括名称空间,文件和Chunk的映射,Chunk的位置信息。Master还管理系统范围内的活动,包括Chunk lease, 孤儿Chunk的回收和Chunk的迁移。
Master节点
master节点通过全局信息定位chunk的位置,传递信息。为了避免master成为系统的瓶颈,我们必须减少对master的读写。客户端并不通过master读写文件数据,而是向master询问它应该联系的Chunk服务器。客户端会将位置信息等元数据缓存一段时间,后续的操作直接在Chunk服务器上进行。
如图所示,客户端把文件名和程序指定的字节偏移,根据固定的Chunk大小,转换成文件的Chunk索引。然后,它把文件名和Chunk索引发送给Master节点。Master节点将相应的Chunk标识和副本的位置信息发还给客户端。客户端用文件名和 Chunk索引作为key缓存这些信息。
之后客户端将请求发送到最近的一个副本处,请求信息包含Chunk的标识和字节范围。在对这个Chunk的后续读取操作中, 客户端不必再和Master节点通讯,除非缓存的元数据信息过期或者文件被重新打开。
Chunk尺寸
Chunk大小是关键的设计参数,GFS选择64MB有如下的优点:
- 减少了客户端和master节点的通讯需求
- 较大的尺寸能容纳多次操作,可以保持较长时间的TCP连接减少网络负载
- 减少了master需要保存的元数据数量,允许将元数据全部存放在内存中
元数据
Master服务器存储3种主要类型的元数据,包括:文件和Chunk的命名空间、文件和Chunk的对应关系、每个Chunk副本的存放地点。所有的元数据都保存在 Master服务器的内存中。前两种类型的元数据同时也会以记录变更日志的方式记录在操作系统的系统日志文件中,日志文件存储在本地磁盘上,同时会被复制到其它的远程Master服务器上。因为元数据保存在内存中,所以Master服务器的操作速度非常快。
操作日志
操作日志是元数据唯一的持久化存储记录,也作为判断同步操作顺序的逻辑时间线。master服务器如果崩溃,需要通过日志将文件系统恢复到最近的状态。为了缩短恢复时间,需要通过Checkpoint缩短日志大小。
当日志增长到一定量时,对系统状态做一次CheckPoint,将所有的状态数据写入CheckPoint文件。在恢复时master从磁盘上读取这个checkpoint文件,再重演checkpoint之后的少数日志即可恢复系统。
一致性模型
数据修改后,文件可能处于不同的状态。如果从各个副本中读取的数据都一样,那么认为文件是**“一致的”。如果修改之后,文件是一致的,并且客户端可以读取写入的全部内容,则认为文件是“已定义的”**。(注:这两者的区别让人迷惑,根据后面写入操作的失败定义,我个人的理解是:“一致”是指从物理层面上看,不同的副本每个bit都是相同的,但是有可能多个写入交织在一起,使得数据混杂在一起,无法被区分,不能正确地被客户端以文件的形式读取。)
数据修改分为写入和记录追加。写入将数据卸载应用程序指定的文件偏移位置上。而记录追加则至少可以把数据原子地追加到文件中一次,但追加的位置由GFS选择,而非固定的文件末尾。GFS返回给客户端一个偏移量,表示写入的起点。
GFS确保被成功修改的文件是已定义的,并且包含最后一次修改操作写入的数据:
- 对Chunk的所有副本的修改操作顺序一致。
- 使用Chunk的版本号来监测副本是否因为服务器宕机而失效。失效的副本不会再进行修改操作,而是被垃圾收集系统尽快回收。
由于Chunk位置信息会被客户端缓存。因此客户端有可能从一个失效的副本读取数据。但因为文件大多数只进行追加操作,一个失效的副本通常返回一个提前结束的Chunk而不是过期的数据。当重新联络master时,客户端会立刻得到最新的Chunk位置信息。
GFS通过Master服务器和所有Chunk服务器的定期“握手” 来找到失效的Chunk服务器,并且使用Checksum来校验数据是否损坏。一旦发现问题,数据要尽快利用有效的副本进行恢复。
lease
变更是一个会改变Chunk内容或者元数据的操作,比如写入操作或者记录追加操作。变更操作会在Chunk的所有副本上执行。我们使用租约 (lease)机制来保持多个副本间变更顺序的一致性。
master为Chunk的一个副本建立一个租约,这个副本即为主Chunk,主Chunk对Chunk的所有更改操作进行序列化,所有副本都遵从这个序列进行修改。修改操作全局的顺序首先由Master节点选择的租约的顺序决定,然后由租约中主Chunk分配的序列号决定。
设计租约机制的目的是为了最小化Master节点的管理负担。如果master节点和主Chunk失去联系,则需要等待该lease过期之后再重新与另一个副本签订协议。
在执行写入操作时:
- 客户机向master节点询问哪一个Chunk服务器持有当前的租约,以及其他副本的位置。如果没有任何一个Chunk持有租约,master会选择一个副本建立租约。Master节点将主Chunk的标识符以及其它副本(又称为secondary副本、二级副本)的位置返回给客户机。客户机缓存这些数据以便 后续的操作。只有在主Chunk不可用,或者主Chunk回复信息表明它已不再持有租约的时候,客户机才需要重新跟Master节点联系。
- 客户机把数据推送到所有的副本上。客户机可以以任意的顺序推送数据。Chunk服务器接收到数据并保存在它的内部LRU缓存中。
- 当所有的副本都确认接收到了数据,客户机发送写请求到主Chunk服务器。这个请求标识了早前推送到所有副本的数据。主Chunk为接收到的所有操作分配连续的序列号,这些操作可能来自不同的客户机,序列号保证了操作顺序执行。
- 主Chunk把写请求传递到所有的二级副本。每个二级副本依照主Chunk分配的序列号以相同的顺序执行这些操作。所有的二级副本回复主Chunk操作的执行状态
- 如果所有的二级副本都返回成功,则这次写入操作成功。如果失败,则这次写操作可能在主Chunk和一些二级副本执行成功。(如果在主Chunk上执行失败,则操作不会被传递)。此时数据不一致,客户端会重复执行失败的操作处理错误。
- 如果应用程序一次写入的数据量很大,或者数据跨越了多个Chunk,GFS客户机代码会把它们分成多个写操作。这些操作都遵循前面描述的控制流程,但是可能会被其它客户机上同时进行的操作打断或者覆盖。因此,共享的文件region的尾部可能包含来自不同客户机的数据片段,尽管如此,由于这些分解后的写入操作在所有的副本上都以相同的顺序执行完成,Chunk的所有副本都是一致的。这使文件region处于一致的、但是未定义的状态。
记录追加
记录追加是原子的。客户机只需要指定要写入的数据,GFS保证至少有一次原子的写入操作成功执行。
记录追加同样遵循lease的控制流程。如果记录追加操作在任何一个副本上失败了,客户端就需要重新进行操作。因此在某个副本上可能存在多个相同的记录。但记录追加最终一定会被成功执行,因此数据一定会写入到Chunk所有副本的相同偏移位置上。(当然在前面可能会有相同的数据)
如图,B最终被成功地追加,而D由于客户端崩溃而没有重复执行。在下一次记录追加的过程中可能被覆盖。
快照
当Master节点收到一个快照请求,它首先取消作快照的文件的所有Chunk的租约。这个措施保证 了后续对这些Chunk的写操作都必须与Master交互交互以找到租约持有者。这就给Master节点一个率先创建Chunk的新拷贝的机会。
租约取消或者过期之后,Master节点把这个操作以日志的方式记录到硬盘上。然后,Master节点通过复制源文件或者目录的元数据的方式,把这条日志记录的变化反映到保存在内存的状态中。新创建的快照文件和源文件指向完全相同的Chunk地址。
在快照操作之后,当客户机第一次想写入数据到Chunk C,它首先会发送一个请求到Master节点查询当前的租约持有者。Master节点注意到Chunke C的引用计数超过了1,则会要求每个拥有Chunk C的副本的Chunk服务器创建一个新的Chunk C\’(确保数据在本地复制,加快处理速度)。再将新的Chunk C\’的主Chunk返回给客户端。
失效副本检测
当Chunk服务器失效时,Chunk的副本有可能因错失了一些修改操作而过期失效。Master节点保存了每个Chunk的版本号,用来区分当前的副本和过期副本。
无论何时,只要Master节点和Chunk签订一个新的租约,它就增加Chunk的版本号,然后通知最新的副本。Master节点和这些副本都把新的版本号记录在它们持久化存储的状态信息中。这个动作发生在任何客户机得到通知以前,因此也是对这个Chunk开始写之前。如果某个副本所在的 Chunk服务器正好处于失效状态,那么副本的版本号就不会被增加。Master节点在这个Chunk服务器重新启动,并且向Master节点报告它拥有的Chunk的集合以及相应的版本号的时候,就会检测出它包含过期的Chunk。如果Master节点看到一个比它记录的版本号更高的版本号,Master节点会认为它和Chunk服务器签订租约的操作失败了,因此会选择更高的版本号作为当前的版本号。
垃圾回收
当一个文件被应用程序删除时,Master节点象对待其它修改操作一样,立刻把删除操作以日志的方式记录下来。但是,Master节点并不马上回收资源,而是把文件名改为一个包含删除时间戳的、隐藏的名字。它们仍旧可以用新的特殊的名字读取,也可以通过把隐藏文件改名为正常显示的文件名的方式“反删除”,直到这些文件被真正删除。即将Master服务器内存中保存的这个文件的相关元数据删除,也就切断了文件和对应Chunk的连接。
在对Chunk名称空间坐常规扫描时,master找到孤儿Chunk并删除它们的元数据。Chunk服务器在和master节点交互时报告自己拥有的子集的信息。master节点回复chunk服务器哪些chunk在master中已经不存在了,chunk服务器可以删除这些副本。
Master服务器的复制
为了保证Master服务器的可靠性,Master服务器的状态也要复制。Master服务器所有的操作日志和checkpoint文件都被复制到多台机器上。对Master服务器状态的修改操作能够提交成功的前提是,操作日志写入到Master服务器的备节点和本机的磁盘。
如果Master 进程所在的机器或者磁盘失效了,处于GFS系统外部的监控进程会在其它的存有完整操作日志的机器上启动一个新的Master进程。
GFS中还有些“影子”Master服务器,这些“影子”服务器在“主”Master服务器宕机的时候提供文件系统的只读访问。影子服务器在运行时会读取一份当前正在进行的操作的日志副本,并且依照和主Master服务器完全相同的顺序来更改内部的数据结构。