学习笔记 - Google分布式文件系统


主要参考:CMU 15-640,以及原论文

1. GFS的硬件基础

  • 大型数据中心,大量的服务器和硬盘以rack为单位,rack之间主要以树型拓扑连接
  • 主要特点
    • 同一rack之间的服务器交流带宽高、延迟低、拥塞情况良好
    • 不同rack之间的服务器交流的有效带宽有限、延迟稍高、拥塞情况随着层级地上升而加重
    • 由于一个数据中心内含有的服务器和硬盘数量巨大(数以千计的服务器、数以百万计的硬盘),硬件错误、崩溃是常有的事情

2. GFS的使用场景假设

  • 存储大文件(>=100MB),小文件也支持,但是不会做优化
  • 两种读操作
    • 较大的顺序读取,例如一次读1MB
    • 较小的随机读取,例如一次读1KB,同样不会做优化
  • 两种写操作
    • 较大的顺序写操作,并且多以record append(找不到合适的中文翻译,下文简称append)为主
    • 较小的随机写操作,同样不会做优化
  • 并发写操作也主要以record append操作为主,这也是整个系统的主要优化方向

注:在后面我们会看到record append操作所具有的并发优势。

3. GFS的设计目标

  1. 保持数据和整体系统的高可用性
  2. 容错机制能够对用户透明
  3. 为了实现系统各部件的同步所需要的overhead尽量小
  4. 高效利用庞大的服务器和硬盘资源
  5. 相比于低延迟,更倾向于维持高吞吐水平

4. GFS的基本框架和主要部件

一个集群(cluster)内包含

  • 一个主服务器(master server)
  • 很多分块服务器(chunk server)

GFS整体框架图

从上图中我们可以得到以下信息

  • 主服务器存储整个文件系统的名称空间以及每一个文件的分块信息
  • 分块服务器是实际存放文件(以块为单位)的机器
  • 客户应用会从主服务器获取有关目标文件的分块信息
  • 在获取分块信息后,客户应用会直接向分块服务器发起数据请求(这一过程与HTTP重定向类似)

4.1 主服务器

  1. 主服务器负责管理该文件系统中的所有文件的元信息(metadata),这些元信息包括

    • 名称空间(namespace),即文件路径

    • 访问控制信息,即文件权限

    • 文件分块信息,包括块编号和块句柄(chunk handle)

    • 实际存储分块数据的分块服务器信息(包括主分块服务器和所有副分块服务器)

  2. 主服务器负责分块数据的迁移

  • 为什么需要进行迁移?
  1. 主服务器负责保持整个文件系统的一致性管理
  2. 主服务器负责垃圾回收所有孤儿分块数据

4.2 分块服务器

  1. 分块服务器负责实际存储文件块,以及文件块对应的版本编号和校验和
  2. 分块服务器职能单一,只需要对客户应用请求所处回应以及定期向主服务器汇报(heartbeat)
  3. 客户应用发出的请求包含对应文件块的块句柄和所请求的数据范围(byte range)
  4. 分块服务器中的每一个文件块都会存在三个备份(默认值),三个会在不同的分块服务器上,通常其中的两个分块服务器会处在同一个rack中,另一个会处在不同的rack中。这样做是既考虑到了分块服务器间通信效率,又考虑到了容灾性能
  5. 分块服务器侧不会缓存数据(除了Linux系统自带的文件缓存机制),同样,客户应用侧也不会缓存数据,客户应用侧只会缓存文件元信息

4.3 客户应用

  1. 客户应用会首先向主服务器发送元信息请求

  2. 在拿到元信息后,客户应用会向所有分块服务器中的某一个发送数据块请求。通常是选择“最近”的一个分块服务器,这里的“最近”是在IP层的意义下的最近

  3. 客户应用侧不缓存文件数据,只缓存文件元信息。从使用场景假设可以看出顺序读取和append操作并不会得益于客户应用侧的缓存机制,并且除去缓存以后也不用担心缓存一致性的问题

  4. 不支持所有的POSIX文件系统接口,支持大多数文件操作,包括创建、删除、打开、关闭、读取和写入

  5. 支持两个特殊的操作

    1. append,这个操作的好处是不需要进行显示的并发同步即可保证并发的append操作不会相互影响
    2. snapshot,这个操作能够很高效地复制一个文件或一个目录结构(即目录下的所有文件)

    关于这两个操作的细节将在后问阐述。

5. GFS的读与写操作

5.1 GFS的读操作流程

GFS读操作的流程

读操作相对比较直观,将之前的框架图稍加标记即可得到一个完整的读操作的执行流程

  1. 客户应用运用已知的固定分块大小,即64MB,以及客户所请求的文件偏移量,即可算出该偏移量所在分块的编号

  2. 客户应用将这个分块编号以及文件名发送到主服务器,请求对应的文件元信息

  3. 主服务器将对应的文件元信息返回给客户应用,其中包括文件块的句柄以及实际存储的分块服务器信息

    客户应用会将该元信息缓存到本地,缓存所用的键是文件名和分块编号

  4. 客户应用向“最近”的分块服务器发送数据请求,指明对应的块句柄和数据范围

  5. 分块服务器返回相应的文件块

注:

  • 在实际实现中,主服务器会多回复一些紧跟在请求文件块之后的文件块元信息,类似于prefetch的思想

  • 云信息的缓存拥有给定的生存时间(Time To Live);亦或者文件在客户应用侧被关闭后再次打开时,元信息缓存也会失效

5.2 GFS的写操作流程

前面提到GFS默认会为每一个文件块保持三个备份,那么当这个文件块需要被修改的时候,如何保持这些备份同步得到更新就是需要解决的一个重要问题。

5.2.1 主要分块服务器

前面我们提到,GFS默认为每一个文件块提供三个分块服务器予以储存。这三个分块服务器有没有层级关系呢?答案是肯定的。其中一个分块服务器将会作为该文件块的主要分块服务器(primary chunk server),其他的为次要分块服务器(secondary chunk server)。这个主要分块服务器的作用就体现在写操作上,在具体到写操作之前,我们先来看看主要分块服务器是如何确立的。

在GFS中,(每一个文件块的)主要分块服务器的身份是由主服务器决定的,并且通过lease来管理这个身份。一个lease通常为60s,即这个时间可以大致理解为各个分块服务器向主服务器汇报自身状态的时间间隔。之所以通过lease来管理,就是为了应对这样一种情况——某个主要分块服务器挂了,那么在lease到期后,主服务器没有收到该主要分块服务器的汇报,那么此时主服务器就可以默认该分块服务器已经不能正常工作了,接下来就会重新从次要分块服务器中选择一个来作为新任的主要分块服务器。

5.2.2 控制流与数据流

GFS中涉及写操作的流程大致可以分为两类,控制流与数据流(如下图所示)。

GFS写操作的流程

  • 数据流

    我们可以看到这里的数据流是一个串型流,而不是类似于广播式的并行流。这样做的考量是由于某一台机器的带宽是有限的,如果这里让Client端发送三分数据到三个分块服务器的话,对Client端带宽的占用会比较大。而使用图中这种串型传递的方式能够有效的分散带宽压力。

    • 从这一点,我们也可以发现整个GFS就是为大文件考虑的,在这种模式下,如果是数据量较小,那么会导致网络延迟增加。但如果数据量较大,那么网络延迟的增加就会很小,甚至还会降低网络延迟(如果接受不了到数据的一端把数据拆分成更小的粒度直接传递下去的话)。
    • 当数据到达分块服务器后,写操作不会马上进行,而是由分块服务器暂时缓存该数据,等待后续的控制流。
  • 控制流

    图中的第一、二步还是和之前相同,即从Master获取元信息。第四步就是客户应用向主要分块服务器发送写操作请求,而剩下的控制流都由该主要分块服务器来引导。其中包括第五步,由主要分块服务器向所有的次要分块服务器发送写操作请求,如果成功,各个次要分块服务器会回复主要分块服务器(第六步),等所有次要分块服务器都回复后,并且自身也完成写操作后,该主要分块服务器就会向客户应用返回成功信息(第七步)。

    • 如果存在多个针对该文件块的并发写操作,由于这些写操作请求都会经过主要分块服务器,因此在主要分块服务器这边会完成顺序的统一,即它会给每个写操作请求编号,要求所有次要分块服务器按照一致的顺序来执行这些并发写操作。
    • 这里也可以看出append操作的原子性,即客户应用在发起append操作时并不会直接指定append的起始偏移量(相对于文件开始的位置),这一决定完全由主要分块服务器来做,即主要分块服务器会分配文件偏移量。这样就可以使得并发append操作有良好的原子性,并且不需要借助于锁的概念。
    • 简单的情况是当append操作的数据量能够装进该文件块。如果不能的话,GFS的选择是对当前文件块进行填充,然后告诉客户应用append到下一个文件块。
    • 并且值得注意的是,在客户应用侧,GFS采取的语义是至少执行一次。这就表明了,如果一次写操作由主要服务器认定为不成功,那么客户应用会再次请求该写操作,直到成功为止。而这种不成功,可能是某一个次要分块服务器的不成功,也可能是主要分块服务器的不成功。因此,例如一次append操作可能带来的结果是同样的数据被append了多于1次,也可能各个分块服务器的append次数不同等等。这个时候,备份之间的一致性就被打破了。后面会讨论GFS如何应对。

6. GFS的容错机制

  • 分块服务器一般有三个备份
  • 主服务器也会有多个备份
  • 一个分块服务器如果崩了,在下一次主服务器进行全局扫描任务的时候,会重新起一个分块服务器
  • 每次主服务器重新给主要分块服务器lease的时候会附加一个版本号,这个版本号也会传给同一个文件块的其他分块服务器
  • 主服务器通过检测版本号来检测是否某个分块服务器已经过时了,即崩溃过、重启过等等,此时该分块服务器就会被主服务器的垃圾回收机制给回收掉。即某个分块服务器一旦出现任何故障,哪怕最后复原了,也会被作为垃圾回收

一些重要的设计及问题

  1. 主服务将所有的元信息都存储在内存(RAM)中,而非硬盘。这样做使得主服务对元信息的读取效率非常快。而这样做的一个潜在隐患即主服务器的内存空间有限,这样做真的好吗?

    由于一个文件对应的元信息相对于文件大小来说是很小的。GFS所选用的分块大小是64MB,而通常一个64MB的文件分块所对应的元信息只有不到64B。并且根据GFS的使用场景假设,大文件居多。因此,这样的元信息所带来的存储overhead是很小的,百万分之一。论文表明实践中内存容量并没有成为限制整个文件系统大小的一个因素。再退一步讲,增加内存容量所需要的成本与将元信息存储在内存中所带来的好处相比是微乎其微的。

    将元信息存储在内存中所带来的读取速度的提升,使得主服务器可以在后台周期性地进行全文件扫描。而这样的扫描是主服务器能够实现垃圾回收、数据块迁移、分块服务器复制等任务的重要前提。

  2. 分块大小为64MB,为什么选取这样一个较大的文件大小?

  3. 关于主要分块服务器的身份管理,不使用lease可以吗?

参考


文章作者: Shun Zhang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Shun Zhang !
  目录