04| GFS论文解读(1)

343 阅读14分钟

尽管网上关于GFS的文章一搜一大把,但是本人作为学习之目的,还是要对GFS做自己的整理、总结和理解输出。一来,这是大数据领域最经典的论文。二来,很多分布式存储系统都是依照的GFS来实现的,比如:HDFS,甚至很多公司所谓自研的分布式存储系统,其实也是依照GFS论文来实现的,这就是经典论文的核心价值。三来,学习下Google是怎么设计GFS的。

The Google File System,即:GFS,这篇论文发表在 2003 年,现在来看这算是一篇“老”论文了。正如前面所述,它指引着很多公司自研的分布式存储系统,是强大而神秘的黑科技。在这篇论文发表之前,工业界的分布式系统最多也就是几十台服务器的集群。而GFS的发表,一下子就拿出了一个可管理1000台服务器以上的分布式文件系统,并且这个文件系统,还会面临外部数百个并发访问的客户端,可谓石破天惊。虽然在今天已经有各种分布式系统,都比当初的GFS更加复杂和强大。回顾这篇论文,我们看GFS在技术思想上闪闪发光,而在其实现上却很保守。通过廉价的PC级别的硬件,就可以搭建出可以处理海量互联网网页数据的系统。而在工程实现上做了大量的取舍(trade-off),也并没有发明什么特别的黑科技。

GFS的产生背景

GFS是Google针对其数据访问量和应用场景而设计的分布式存储系统,其特点:

1)数据量大:海量数据,大文件为主,每个文件有几百MB,甚至几GB大小。系统支持小文件,但不需要针对小文件做专门的优化。

2)数据访问的特点是做数据分析:因此,顺序访问比较多,需要顺序遍历数据文件,大规模的流式读取数据。

3)写操作以高效且原子性的追加写操作,文件多用于生产者-消费者模型,或多路归并操作。数据一旦被写入之后,文件就很少再被修改。

4)高性能的网络带宽远比低延时重要。因为,大部分情况下要求能够高效率、大批量地处理数据,对单一的读写操作的时间响应要求较低。

5)系统由廉价的普通机器硬件组成,因此,节点失效是常态,GFS必须有较高的容错性,能够持续监控自身的状态,还能从节点失效导致的异常中快速恢复。

GFS的架构

架构图如下图所示:

image-39.png

从架构图中可以看到,一个GFS集群包含一个Master节点、多个Chunk Server服务器、 以及允许被多个Client客户端访问。所有的机器资源都是普通的Linux服务器,只要机器资源够用Chunk Server服务器和Client客户端都放在同一台服务器上。

存储文件时,GFS将文件分割成固定大小的Chunk,在Chunk创建时,Master服务器会给每个Chunk分配一个不变的、全球唯一的64位标识——Handle句柄,Chunk Server把Chunk以普通Linux文件的形式保存在本地硬盘上,并且根据指定的Handle句柄和字节范围来读写数据。为了避免节点异常带来的数据损坏,GFS把每个Chunk以副本Replica的方式冗余备份到不同的Chunk Server服务器上。默认使用3副本策略,用户可以为不同的文件命名空间设定不同的复制级别。

Master负责维护整个集群的元数据,包括集群的名字空间Namespace、访问控制信息、文件和Chunk的映射信息、当前Chunk的位置信息、Chunk Lease租约管理、Chunk回收、Chunk在服务器之间的迁移等系统级操作。Master节点通过心跳信息周期性地跟每一个Chunk Server服务器通信,发送指令到各个Chunk Server服务器,并接收各个Chunk Server服务器的状态信息。

Client客户端代码以库的形式被链接到客户程序中,客户端代码实现了GFS文件系统的API接口函数、应用程序与Master节点和Chunk Server服务器通信、以及对数据进行读写操作。Client客户端与Master节点的通信只是获取元数据,所有的数据操作都是Client客户端直接跟Chunk Server服务器进行交互的,如上图所示,数据流和控制流是分开的,这样就避免了Master节点成为性能瓶颈。

Client客户端和Chunk Server服务器都不需要缓存文件数据,Client客户端只需要缓存元数据,因为大部分文件要么以流的方式被读取,要么太大而无法缓存。而Chunk Server服务器本身就是以Linux文件的形式存储数据,其Linux内核的Page Cache页缓存会缓存文件数据的。

GFS的设计决策

从上面的架构图,我们可以看到Google设计的几个原则:简单原则、根据硬件特性做设计取舍、根据实际应用做设计取舍

第一个,简单设计原则:

首要的简单是文件存储就是普通的Linux文件,充分利用了Linux特性。其次简单是单一的Master节点策略,这个就大大简化了设计,单Master让整个GFS的架构变得非常简单,避免了复杂的一致性问题。当然这也带来了很多限制,比如:一旦Master出现故障,整个集群就无法写入数据,而恢复Master就需要人工介入,从这一点来看,GFS Master节点并不是一个高可用的系统。那么怎么提高可用性呢?工程实现上有很多手段,GFS采用了Checkpoint、操作日志,引入影子节点Shadow Master等一系列手段,这再次印证了计算机技术实现上“扬长避短”的思想,发挥其长处,针对短处,采取一系列手段去优化弥补。

第二个,根据硬件特性做设计取舍:

首先,在GFS论文发表的年代,大家还在使用机械硬盘,对机械硬盘,我们都知道其随机读写的性能很差,所以GFS的设计中主要是顺序读写操作,以Append追加的方式写入数据,以流式访问读取数据。

其次,当时的数据中心,服务器的网卡带宽普遍是百兆网卡,网络带宽往往是系统的性能瓶颈,因此,GFS设计使用流水线式的数据传输机制。

第三,在文件复制操作时,GFS专门设计了一个Snapshot,其目的也是为了避免复制时数据在网络上传输。这些设计都是避免有限的网络带宽成为性能瓶颈。

第三个,根据实际应用做设计取舍:

GFS是为了在廉价硬件上做大规模数据处理而设计的,所以GFS的一致性相当宽松,GFS对随机写入的一致性没有任何保障,对于Append追加操作,GFS也只是做了“At Least Once”至少一次的保障。这个任务被交给了客户端,通过在客户端中实现校验、去重这样的处理机制,这样GFS在大规模数据处理上也可以很好适用。

 

下面我们根据这些设计原则进一步分析GFS的论文,第一个简单原则。GFS是单Master节点策略,非常简单,但是这个Master实际上有三种不同的身份,分别是:

1)如果从存储数据的Chunk Server来看,Master是一个目录服务。

2)如果从灾难恢复的Backup Master,Master是一个符合主从架构的同步复制的主节点。

3)如果从保障读数据的可用性而设计的Shadow Master,Master是一个符合主从架构的异步复制的主节点。

当我们要把一个文件存储在GFS上的时候,GFS会通过“命名空间+文件名”来定义一个文件,比如:/data/bigdata/gfs01,这样所有GFS的客户端都可以通过/data/bigdata/这个命名空间,加上文件名gfs01,去读写这个文件。这时就会有一个问题:读写的时候,这个文件到底存储在哪个服务器上呢?客户端具体是怎么读到这个文件的呢?

在GFS中有两种节点:Master和Chunk Server。Master有且只有一个主控节点,Chunk Server就是实际存储数据的节点,因为GFS是分布式文件系统,因此,文件是存储在不同的服务器上的。GFS把文件按照64MB一块的大小,切分成一个个的Chunk块,每个Chunk都会有一个在GFS上的唯一的handle句柄,这个handle句柄就是一个64位编号,能够唯一标识出一个Chunk,每个Chunk都会以文件的形式存放在Chunk Server上。而Chunk Server就是普通的Linux服务器,运行GFS的Chunk Server程序,它负责和Master以及Client进行RPC通信,完成实际的数据读写操作。为了确保数据的可靠性,不至于因为某个Chunk Server服务器挂掉就出现数据丢失,每个Chunk都会有3个副本,其中一份是主数据Primary,两份是副数据Secondary,当三份数据出现不一致时,就以主数据为准。副本技术不仅可以防止各种异常出现的数据丢失,还能在并发读取时,分担系统读压力。这样一来,文件就被拆分成了一个个的Chunk存在Chunk Server上,那么Client怎么知道去哪个Chunk Server上找自己要的文件呢? 这就用到Master了。因此,我们看下Master中存储的元数据metadata信息:

1)文件和Chunk的命名空间信息,即:/data/bigdata/gfs01,这样的命名空间和文件名。

2)文件被拆分成了哪几个Chunk,即:命名空间文件名到多个Chunk Handle句柄的映射关系。

3)这些Chunk实际存储在了哪些Chunk Server上,即:Chunk Handle句柄到Chunk Server服务器的映射关系。

image-40.png

当客户端去读GFS里面的数据的时候,需要怎么做呢?

客户端先去问Master节点:我要读取的数据在哪里?客户端会把文件名和数据范围(offset和length)发送到Master上,因为文件被按照固定大小(64MB)切割成chunk了,所以很容易计算出要读取的数据在哪几个chunk里面,客户端就会告诉Master,我要读取哪个文件的第几个chunk。Master把这个chunk对应的所有副本所在的chunkserver告诉客户端,客户端拿到这个信息后,就可以去任意一个chunkserver上读取所需的数据。

其实整个过程本质上跟Linux文件系统差不多,核心思想都是一脉相承,Master节点就好比superblock和所有的inode,chunk就是文件系统中的block块,只不过尺寸大了些,且分布在不同的机器上。客户端通过Master节点读取数据的过程,就好比文件系统中通过inode查找block,然后从指定block读取数据的过程。所以这个时候的Master就是一个“目录服务”,Master节点自身并不存储数据,而是存储“目录”这样的元数据信息,这跟单机上文件系统的设计思想是一样的。

很多大型复杂的系统设计都是从最简单、最底层的技术和系统演化而来的,为了特定的功能或应用场景进行针对性的优化,扬长避短。 再比如像数据结构和算法中的数组和指针,由二者演化出很多的复杂数据结构和算法,由数组的二分查找演化出链表的二分查找(跳表)。下面我们来看简单的长处背后是如何避短的:Master节点的容错性和可用性保障

Master简单的背后是有代价的,我们能看到,在这个设计中,Master节点压力很大,很容易成为性能瓶颈,如果几百个客户端并发读取数据,都要去Master上找,这就要求Master节点把所有的数据都存储在内存中,这样Master的性能才能跟得上,但是数据放在内存中问题也很明显,一旦Master节点重启或挂掉,内存中的数据就丢失了,怎么办?通过记录操作日志和定期生成对应的Checkpoint进行持久化,也就是存储到磁盘上,这样就不怕内存因故障而数据丢失了。当Master重启时,就先读取最新的Checkpoint,然后进行回放(replay)操作日志,这样就可以让Master节点恢复到之前最新的状态,这就是存储系统常用的可恢复机制。

仅仅能恢复内存数据也不靠谱,如果Master节点机器故障无法恢复呢?这个时候就要考虑准备“备胎”了。比如:增加Backup Master,所有针对Master的操作都同时写入Backup Master上,只有Master上操作成功且写入磁盘成功,还有Backup Master上也操作成功且写入磁盘成功,整个操作才视为成功。这种方式叫做 “同步复制” ,是分布式数据系统中一种典型的模式。比如在Mysql中也经常这样使用,高可用的Mysql一般都是主从架构的同步复制模式。当Master节点故障后,Backup Master节点便会转正,变成新的Master,而里面的数据都是一摸一样的。那么怎么能检测到Master挂掉呢?这就需要一个监控程序,监控Master服务的运行状态,如果只是master服务挂了,只要重启master服务;如果是Master节点故障,就选择一个Backup Master节点成为新Master。为了让集群中其他Chunk Server和客户端不用感知这个Master切换的变化,GFS通过一个规范名字(Canonical Name)来访问Master节点,这类似于DNS别名,这样,一旦要切换Master,这个监控程序只需要修改别名实际指向,访问新的Master节点就能达到目的。有了这个机制,GFS的Master就可快速恢复(Fast Recovery)。

尽管如此,对于几百个客户端访问的GFS,仍然面临在故障恢复过程中无法读取的问题,这个该怎么解决呢?办法就是加入一些只读的影子节点Shadow Master,这些Shadow Master节点不同于Backup Master节点,Master写入数据并不需要等待这些Shadow Master节点也写入成功,而是这些Shadow Master节点不断地从Master节点上同步数据,这种方式叫做 “异步复制” ,是分布式数据系统中另一种典型的模式。在异步复制模式下,这些Shadow Master节点上的数据跟Master节点上的数据并不是完全同步的,而是可能有小小的延迟。这可能导致这些Shadow Master节点上元数据信息不是最新的,但实际上,这种情况发生的概率还是很小的,因为这需要同时满足三个条件:1)Master节点挂掉;2)Shadow Master节点没有同步完最新的操作日志和Checkpoints;3)恰好要读取的那部分数据就是没有同步完的部分。

image-41.png

实际上,Shadow Master为了保持自身状态是最新的,他会读取一份当前正在进行的操作日志副本,并依照跟Master完全相同的顺序更新内部数据。Shadow Master在启动时也会从Chunk Server服务器上轮询数据(之后定期拉数据),数据包括副本的位置信息。Shadow Master也会定期和Chunk Server服务器“握手”来确定他们的状态,只有在Master节点因为创建和删除副本导致副本的位置信息变化时,Shadow Master才和Master通信更新自身状态。通过Backup Master和Shadow Master,GFS构建了高可用(HA)架构。

 

通过这些分析和解读,我们看到简单设计原则的使用,希望我们自己做的系统设计也能遵从“简单原则”,让系统更加容易维护和迭代。

===========================================================