1 设计概述
1.1 假设
①系统是构建在普通的、廉价的机器上,因此故障是常态而不是意外
②系统希望存储的是大量的大型文件(单个文件size很大)
③系统支持两种类型读操作:大量的顺序读取以及小规模的随机读取
④系统的写操作主要是顺序的追加写,而不是覆盖写
⑤系统对于大量客户端并发的追加写有大量的优化,以保证写入的高效性与一致性,主要归功于原子操作
⑥系统更看重的是持续稳定的带宽而不是单次读写的延迟
1.2 GFS架构
GFS由三部分组成:
Master、chunkserver、client。
GFS master任意时刻只有一个,而chunkserver和GFS client可能有多个
①GFS 存储的文件都被分割成固定大小的 Chunk(64MB),每个chunk有全局唯一的文件句柄 : 一个64位的chunk ID,每一份chunk会被复制到多个chunkserver(默认值是3)
②GFS master是系统的元数据服务器,维护的元数据包括:命令空间(GFS按层级目录管理文件)、文件到chunk的映射,chunk的位置。其中,前两者是会持久化的,而chunk的位置信息来自于Chunkserver的汇报。
③GFS master还负责分布式系统的集中调度:chunk lease管理,垃圾回收,chunk迁移等重要的系统控制。master与chunkserver保持常规的心跳,以确定chunkserver的状态。
1.3 读取流程
客户端向 Master 节点询问它应该联系的 Chunk 服务器。客户端将这些元数据信息缓存一段时间,后续的操作将直接和 Chunk 服务器进行数据读写操作。
读取的流程如下:
①应用程序调用GFS client提供的接口,表明要读取的文件名、偏移、长度。
②GFS Client将偏移按照规则翻译成chunk索引,发送给master
③master将chunk id与chunk的副本位置告诉GFS client
④GFS client向最近的持有副本的Chunkserver发出读请求,请求中包含chunk id与字节范围
⑤ChunkServer读取相应的文件,然后将文件内容发给GFS client。
1.4 元数据
三种类型的元数据::文件和 Chunk 的命名空间、文件和Chunk 的对应关系、每个 Chunk 副本的存放地点。所有的元数据都保存在 Master 服务器的内存中。前两种类型的元数据同时也会以记录变更日志的方式记录在操作系统的系统日志文件中,日志文件存储在本地磁盘上。Master向各个 Chunk 服务器轮询它们所存储的 Chunk 的信息,这样更简单,简化了在有 Chunk 服务器加入集群、离开集群、更名、失效、以及重启的时候,Master 服务器和 Chunk 服务器数据同步的问题
1.5 操作日志
操作日志会被复制到多台远程机器上,只有把相应的日志记录写入到本地以及远程机器的硬盘后,才会响应客户端的操作请求。
Master 服务器在灾难恢复时,通过重演操作日志把文件系统恢复到最近的状态,但是大量的日志在恢复过程中速度较慢,因此引入checkpoint。
Master 服务器在日志增长到一定量时对系统状态做一次 Checkpoint,将所有的状态数据写入一个 Checkpoint 文件,在 Checkpoint 过程中不会阻塞正在进行的修改操作。
Master 服务器恢复只需要最新的 Checkpoint 文件和后续的日志文件。旧的 Checkpoint 文件和日志文件可以被删除,但是为了应对灾难性的故障,通常会多保存一些历史文件。恢复功能的代码可以检测并跳过没有完成的 Checkpoint 文件。
1.6 一致性模型
GFS提供的一致性保证称之为“relaxed consistency”,弱一致性。
两个术语:consistent,defined
consistent:对于文件区域A,如果所有客户端从任何副本上读到的数据都是相同的,那A就是一致的。
defined:如果A是一致的,并且客户端可以看到写入的全部完整数据,那A就是defined,即结果是可预期的。
①对于写操作(写入操作把数据写在应用程序指定的文件偏移位置上),如果是顺序写,那么一定是defined;如果是并发写,那么各个副本之间一定是一致的,但结果是undefined的,可能会出现相互覆盖的情况。
②使用GFS提供的record append这个原子操作,至少可以把数据原子性的追加到文件中一次,但是偏移位置是由GFS 选择,内容也一定是defined。但是会interspersed with inconsistent(部分不一致),因为如果某个chunkserver写入数据失败,都会重试写入流程,这就导致chunk中有一部分数据在不同的副本中是不一致的。
③失败的修改操作导致一个 region 处于不一致状态(同时也是未定义的):不同的客户在不同的时间会看到不同的数据
2 系统交互
2.1 契约机制
GFS采用中心化副本控制协议,即对于副本集的更新操作有一个中心节点来协调管理,将分布式的并发操作转化为单点的并发操作,从而保证副本集内各节点的一致性。使用租约(lease)机制选举中心节点,与master建立租约的副本称为primary(中心节点),其他的副本称为secondary(非中心节点)。
租约的初始超时设置为 60 秒。只要Chunk 被修改了,primary可以申请更长的租期,通常会得到 Master 节点的确认并收到租约延长的时间。
这些租约延长请求和批准的信息通常都是附加在 Master 节点和 Chunk 服务器之间的心跳消息中来传递。有时Master 节点会试图提前取消租约。即使Master节点和主Chunk失去联系,它仍然可以安全地在旧的租约到期后和另外一个Chunk副本签订新的租约。
2.2 数据写入过程
①client向 Master 节点询问哪一个 Chunk 服务器持有当前的租约,以及其它副本的位置。如果没有一个Chunk 持有租约,Master 节点就选择其中一个副本建立一个租约,即选择一个primary
②master将primary的标识符以及其它副本(secondary)的位置返回给client,client缓存这些信息在本地
③client将数据链式推送到所有副本
④当所有的副本都确认接收到了数据,client发送写请求到primary,这个请求标识了早前推送到所有副本的数据。primary为接收到的所有操作分配连续的序列号,它以序列号的顺序在本地执行这些操作。
⑤primary在自己成功提交后,把写请求传递到所有的secondary,通知所有secondary提交
⑥Secondary向Primary回复提交结果
⑦primary回复client。任何副本产生的任何错误都会返回给client。在出现错误的情况下,写入操作可能在primary和一些secondary执行成功。client通过重复执行失败的操作来处理这样的错误。
如果应用程序一次写入的数据量很大,或者数据跨越了多个 Chunk,GFS 客户机代码会把它们分成多个写操作。
2.3 数据流
GFS将数据流与控制流分离
控制流从客户机到主 Chunk、然后再到所有二级副本的同时
数据流以管道的方式,顺序的沿着一个精心选择的 Chunk 服务器链,线性推送。每台机器都尽量的在网络拓扑中选择一台还没有接收到数据的、离自己最近的机器作为目标推送数据
GFS使用基于TCP连接的、管道式数据推送方式传输数据,以最小化延迟。一旦chunkserver收到数据,立刻开始推送,即一个replica不用收到完整的数据再发往下一个replica。
另一种数据推送方式为主从模式,如上图
Client首先将数据推送到Primary,再由Primary推送到所有secodnary。很明显,Primary的压力会很大
2.4 原子的记录追加
使用记录追加,客户机只需要指定要写入的数据,GFS 保证至少有一次原子的写入操作成功执行,写入的数据追加到 GFS 指定的偏移位置上,之后 GFS 返回这个偏移量给客户机。
如果记录追加操作在任何一个副本上失败了,客户端就需要重新进行操作。重新进行记录追加的结果是,同一个Chunk的不同副本可能包含不同的数据,即重复包含一个记录全部或者部分的数据。GFS并不保证Chunk的所有副本在字节级别是完全一致的。
对于原子的记录追加操作,如果操作成功执行,数据一定已经写入到 Chunk 的所有副本的相同偏移位置上。
如下图: