MIT 6.824 2018 Lec 3: GFS

本篇文章主要是对阅读《The Google File System》论文后做一个总结.

介绍

《The Google File System》是非常经典的一篇论文,HDFS 就是基于这篇论文设计实现的。GFS 是谷歌为满足大型分布式的数据敏感型应用而开发的分布式文件系统,论文就是介绍了 GFS 的设计模型和原理。

一致性

在分布式系统中,节点由于各种因素发生故障是很常见的问题,如果数据只存放在单个节点上,那么一旦节点发生故障,很容易因为单点故障而造成数据丢失。因此给一份数据复制几个副本是很普遍的做法,即数据冗余。但是这又引出了数据一致性的问题,尤其是不同的应用通常会并发地对数据进行读写,如何保证数据冗余中每一份副本的内容是完全一致的,这是个难题。

一致性有两种,各有优劣:

弱一致性:

  • 读取的数据可能是过期的,即不是最近一次写入的最新数据。

  • 系统有较好的性能,扩展到多台服务器比较容易。

  • 但是运行情况复杂,出问题时比较难搞清楚。


强一致性:

  • 读取的数据总是最新的。

  • 实现应用的写操作比较简单。

  • 但是会影响系统性能。

一致性模型

根据实际情况,我们必须对系统的一些特性做取舍,这也就产生了不同的一致性模型,理想的一致性模型就是,当我们使用有数据冗余的分布式文件系统时,感觉就像是在一台机器上使用没有数据冗余的普通文件系统,即不会有读写数据不一致的情况。

在一台机器上,在写入数据后,就能读取最新写入的数据。如果两个应用并发地对一个文件进行写操作,那么这个文件就是undefined状态,因为文件中可能混合了两个应用写入的数据。如果两个应用并发地对一个目录进行写操作,目录会被加锁,一个应用先写,然后再是另一个应用。

在分布式系统中,实现理想的一致性模型有很多的问题:

  • 并发操作
  • 节点故障,在节点上的任何操作都有失败的可能
  • 网络分割,不是所有的机器/磁盘都是可达的


要解决这些问题,需要花费的代价是很高的:

  • 客户端与服务端的频繁通信会对性能造成很大的影响
  • 维持系统一致性的协议复杂,难以实现


所以很多系统实际上都没有实现理想的一致性,GFS 也是如此。

GFS 实现目标

分布式系统在节点众多的情况下,故障是很常见的,因此 GFS 必须有容错性。

GFS 要是高性能的,使用场景中有许多并发的读写任务。

GFS 要有效利用网络,节省带宽。

GFS 读操作

文件的命名空间以目录树的形式保存在 master 上,每个文件都分为固定大小(64MB)的块保存在 chunkserver 上,每个块都默认有三个副本保存在不同的 chunkserver 上,这不仅提高了可用性,也能对热点文件的读取进行更好的负载均衡。

master 将操作日志保存在本地磁盘中,可以从电源故障中快速恢复。如果 master 不可用,那么选定一个 shadow master 作为新的 master。

客户端读取文件的过程:

  • 将文件名和块索引发送给 master
  • master 返回保存对应块的服务器集合,包括块的版本号
  • 客户端将返回的信息放入缓存
  • 客户端访问最近的 chunkserver,如果块的版本号出错,就再次请求 master

GFS 写操作

客户端随机写的流程:

  • 客户端询问 master 块的位置和当前哪个块是 primary,primary 决定对块写操作的顺序,其他块的副本都以这个顺序执行写操作
  • 客户端根据网络拓扑结构,确定传输数据的链路,即数据在不同 chunkserver 间传输的先后顺序
  • 客户端根据确定的传输链路,先将数据传输给第一个块副本,与此同时,块副本将接收到的数据传给下一个块副本,这个传输方式可以充分利用每个 chunkserver 的带宽,提高数据传输的效率
  • 接收到数据的块副本向客户端发送 ack
  • 客户端收到 ack 后,向 primary 发出写命令,primary 确定写操作的顺序,开始写操作,并告诉其他块副本以相同的顺序进行写操作,写操作完成后发送 ack 给客户端


如果有另一个客户端并发地对统一文件进行写操作,那么有以下几种情况:

  • 客户端B在客户端A写完后进行写操作,将客户端A写的内容覆盖
  • 客户端B被调度到客户端A之前开始写,因为客户端A可能运行速度较慢
  • 客户端A在客户端B写完后进行写操作,将客户端B写的内容覆盖


以上三种情况,所有的块副本都包含相同的数据,即是一致的,但是数据可能混合了客户端A和客户端B写入的数据,即是 undefined。

客户端 append 流程:

  • 流程与上述一致,但是客户端A和客户端B append 的内容可能是任意顺序的(因为并发)

  • 所有块副本是一致的,但是也是 undefined

  • 如果只有一个客户端,那数据就是一致且确定的(defined)

Record Append

在上述的两种写操作中,数据的写入位置是由客户端指定的,但是 GFS 提供了另一种叫做 record append 的写操作,是原子操作,数据的写入位置是由 GFS 而不是客户端确定的。

record append 的流程如下:

  • 客户端将数据传给所有的块副本后,与 primary 通信
  • primary 确定写操作顺序
  • primary 检查块大小是否满足 append 的数据,如果不满足,就先将块写满,并告诉客户端需要将剩余数据写到另一个块中
  • primary 选择 append 的位置
  • primary 在本地进行 append
  • primary 将 append 操作传递给其他块副本


如果写操作失败,假设第三个块副本 R3 失败了,那么客户端要重新尝试:

  • master 选择另一个块副本 R4 替代 R3,或者 R3 已经恢复正常,还是选择R3
  • 因为另外两个副本已经 append 成功,所以必须选择一个所有块都能 append 的新位置
  • primary 和其他块副本进行写操作
  • primary 在收到所有副本的 ack 后,向客户端返回写入成功的信息

 

如果写操作失败后,经过重试才成功,那么块副本中是存在重复的数据的,record append 只保证所有数据至少写入一次。

总结

对于文件来说,GFS 不是理想的一致性模型,为了性能牺牲了一些一致性,但在它的使用场景里是可以接受的,如使用 GFS 的应用,存在大量并发地对同一个文件进行 append 操作,record append 可以提供很好的写入性能,虽然可能会导致部分块副本有重复的数据而不一致,但这是后续通过程序处理的,所以可以接受这部分的不一致性。

对于目录来说,GFS 是强一致性的,因为只保存在 master 上,但 master 不是高可用的,且扩展性有限。

客户端可能会在以下情况读到过期的数据:

  • primary chunkserver 在更新块之后发生故障,其他块副本还没来得及进行相同的更新操作。
  • 因为客户端在读操作是,会缓存 master 返回的信息,所以可能会因为缓存的信息过期而读到过期的块

版权声明

作者:萝卜姓胡
许可协议:Creative Commons Attribution-ShareAlike 4.0 International License
本文永久链接:http://hw2007.com/2018/11/27/MIT-6-824-2018-Lec-3-GFS/