整理:Google文件系统

来源:互联网 发布:苹果mac顿号怎么打 编辑:程序博客网 时间:2024/06/04 18:08

一个面向大规模数据密集型应用的、可伸缩的分布式文件系统;以应用的负载情况和技术环境的分析为基础的;最大的一个集群利用数千台机器的数千个硬盘,提供了数百TB的存储空间,同时为数百个客户机服务

GFS的主要假设如下:

  • GFS的服务器都是普通的商用计算机,并不那么可靠,集群出现结点故障是常态。因此必须时刻监控系统的结点状态,当结点失效时,必须能检测到,并恢复之
  • 系统存储适当数量的大文件。理想的负载是几百万个文件,文件一般都超过100MB,GB级别以上的文件是很常见的,必须进行有效管理。支持小文件,但不对其进行优化
  • 负载通常包含两种读:大型的流式读(顺序读)和小型的随机读。前者通常一次读数百KB以上,后者通常在随机位置读几个KB
  • 负载包括很多连续的写操作,往文件追加数据。文件很少会被修改,支持随机写操作,但不必进行优化
  • 系统必须实现良好定义的语义,用于多客户端并发写同一个文件。同步的开销必须保证最小
  • 高带宽比低延迟更重要,GFS的应用大多需要快速处理大量的数据,很少会严格要求单一操作的响应时间

 上述假设基本可以看出GFS期望的应用场景应该是大文件,连续读,不修改,高并发。国内的淘宝文件系统就不一样,专门为处理小文件进行了优化

体系结构

GFS包括一个master结点(元数据服务器),多个chunkserver(数据服务器)和多个client(运行各种应用的客户端)。可靠性要求不高的场景,client和chunkserver可以位于一个结点。每一结点都是普通的Linux服务器,GFS的工作就是协调成百上千的服务器为各种应用提供服务

  • chunkserver提供存储。GFS会将文件划分为定长数据块,每个数据块都有一个全局唯一不可变的id(chunk_handle),数据块以普通Linux文件的形式存储在chunkserver上,出于可靠性考虑,每个数据块会存储多个副本,分布在不同chunkserver
  • GFS master是GFS的元数据服务器,负责维护文件系统的元数据,包括命名空间、访问控制、文件-块映射、块地址等,以及控制系统级活动,如垃圾回收、负载均衡等
  • 应用需要连接client的代码,然后client作为代理与master和chunkserver交互。master定期与chunkserver交流(心跳),以获取chunkserver的状态并发送指令

图1 描述了应用读取数据的流程:

  • 应用指定读取某个文件的某段数据,因为数据块是定长的,client可以计算出这段数据跨越了几个数据块,client将文件名和需要的数据块索引发送给master
  • master根据文件名查找命名空间和文件-块映射表,得到需要的数据块副本所在的地址,将数据块id和其所有副本的地址反馈给client
  • client选择一个副本,联系chunkserver索取需要的数据
  • chunkserver返回数据给client

数据的布局

GFS将文件条带化,按照类似RAID0的形式进行存储,可以提高聚合带宽。事实上,大多数分布式存储系统都会采取这种策略。GFS将文件按固定长度切分为数据块,master在创建一个新数据块时,会给每个数据块分配一个全局唯一且不可变的64位id。每个数据块以Linux文件的形式存储在chunkserver的本地文件系统里

GFS为数据块设置了一个很大的长度64MB,比传统文件系统的块长要大多了。大块长会带来很多好处:

  • 减少client和master的交互次数,因为读写同一个块只需要一次交互,在GFS假设的顺序读写负载的场景下特别有用
  • 同样减少了client和chunkserver的交互次数,降低TCP/IP连接等网络开销
  • 减少了元数据的规模,因此master可以将元数据完全放在内存,这对于集中式元数据模型的GFS尤为重要

大数据块也有缺点。最大的缺点可能是内部碎片,不过考虑到文件一般都相当大,所以碎片只存在于文件最后一个数据块。另外一个缺点:由于小文件可能只有少量数据块,极端情况只有一个,那么当小文件是热点文件时,存储该文件数据块的chunkserver可能会负载过重。不过正如前面所说,小文件不在GFS的优化范围

为了提高数据的可靠性和并发性,每一个数据块都有多个副本。当客户端请求一个数据块时,master会将所有副本的地址都通知客户端,客户端再择优(距离最短等)选择一个副本。一个典型的GFS集群可能有数百台服务器,跨越多个子网,因此在考虑副本的放置时,不仅要考虑机器级别的错误,还要考虑整个子网瘫痪了该怎么办。将副本分布到多个子网去,还可以提高系统的聚合带宽。因此创建一个数据块时,主要考虑几个因素:1.优先考虑存储利用率低于平均水平的结点;2.限制单个结点同时创建副本的数量;3.副本尽量跨子网

元数据服务

GFS是典型的集中式元数据服务,所有的元数据都存放在一个master结点内。元数据主要包括三种:文件和数据块的命名空间、文件-数据块映射表、数据块的副本位置,所有的元数据都是放在内存里的。前两种元数据会被持久化到本地磁盘,以操作日志的形式。操作日志记录两种元数据的每一次关键变化,因此当master宕机,可以根据日志恢复到某个时间点。日志的意义还在于: 提供了一个时间线,用于定义操作的先后顺序,文件、数据块的版本都依赖于时间顺序

数据块的副本位置没有持久化,因为动辄数以百计的chunkserver是很容易出错的,因此只有chunkserver对自己存储的数据块有绝对的话语权,而master上的位置信息很容易因为结点失效等原因而过时。取而代之的方法是,master启动时询问每个chunkserver的数据块情况,而且chunkserver在定期心跳检查中汇报自己存储的部分数据块情况。GFS物理上没有目录结构,也不支持链接操作,使用一张表来映射文件路径名和元数据

缓存和预取

GFS的客户端和chunkserver都不会缓存任何数据,因为GFS的典型应用是顺序访问大文件,不存在时间局部性。空间局部性虽然存在,但是数据集一般很大,以致没有足够的空间缓存

集中式元数据模型的元数据服务器容易成为瓶颈,应该尽量减少客户端与元数据服务器的交互,因此GFS设计元数据缓存。 client需要访问数据时,先询问master数据在哪儿,然后将这个数据地址信息缓存起来,之后client对该数据块的操作只需直接与chunkserver联系,当然缓存的时间是有限的,过期作废

master支持元数据预取。因为空间局部性是存在,master可以将逻辑上连续的几个数据块的地址信息一并发给客户端,客户端缓存这些元数据,以后需要时就可以不用找master

异常处理

引用:“We treat component failures as the norm rather than the exception."

分布式系统整体的可靠性是至关重要的。GFS集群使用的都是普通商用计算机,而且机器的数量众多,设备故障经常出现,如何处理结点失效的问题是GFS最大的挑战

  1. 完整性

GFS使用数以千计的磁盘,磁盘出错导致数据被破坏时有发生。可以用其它副本来恢复数据,但首先必须能检测出错误。 chunksever使用校验和来检测错误数据。每一个块(chunk)都被划分为64KB的单元(block),每个block对应一个32位的校验和。校验和与数据分开存储,内存有一份,然后以日志形式在磁盘备份。

chunkserver发送数据之前会核对数据的校验和,防止错误的数据传播出去。如果校验和与数据不匹配,就返回错误,并且向master反映情况。master会开始克隆副本的操作,完成后就命令chunkserver删除非法副本

  1. 一致性

一致性指的是master元数据和chunkserver数据是否一致,多个数据块副本之间是否一致,多个客户端看到的数据是否一致。先来看看元数据一致性。GFS的命名空间操作是原子性的,并且用日志记录下操作顺序。虽然GFS没有目录结构,但是仍然有一颗逻辑的目录树,树的每个结点都有自己的读写锁,每个元数据操作都需要获得一系列的锁,应该是写锁会阻塞其它的锁,而读锁只阻塞写锁而不阻塞读锁。比如/home/user目录正在创建快照,需要获得/home的读锁和/home/user的写锁,如果想创建文件/home/user/foo会被阻塞,因为需要获得/home、/home/user的读锁以及/home/user/foo的写锁,快照会阻塞创建操作获取/home/user的读锁。如果是在一个有传统目录树结构的文件系统里,创建一个文件需要修改父目录的数据,需要获得父目录的写锁。这种锁机制允许在一个目录里并发修改数据(如并发创建文件等),这在传统文件系统里是不允许的。

再来看看GFS如何并发写:GFS必须将对数据块的修改同步到每一个副本。考虑一下多个应用同时修改同一数据块的情况,我们必须为修改操作定义统一的时序,不然多个副本会出现不一致的情况,那么定义时序由谁做呢?还记得前面提到的元数据缓存吗,为了减少master的负担,client在获得副本位置后就不再和master交互,所以必然需要选出一个master代理来完成这个任务。事实上GFS采用了租约(lease)的机制,master会将租约授权给某个副本,称为primary,由这个primary来确定数据修改的顺序,其它副本照做就是。

图2是写操作的控制流和数据流:

  • client需要更新一个数据块,询问master谁拥有数据块的租约(谁是primary)
  • master将持有租约的primary和其它副本的位置告知client,client缓存之
  • client向所有副本传输数据,这里副本没有先后顺序,根据网络拓扑情况找出最短路径,数据从client出发沿着路径流向各个chunkserver,这个过程采用流水线(网络和存储并行)。chunkserver将数据放到LRU缓存
  • 一旦所有的副本都确定接受数据,client向primary发送写请求,primary为这个前面接受到的数据分配序列号,即primary为所有写操作分配连续序列号表示先后顺序,并且按照顺序执行数据更新
  • primary将写请求发送给其它副本,每个副本都按照primary确定的顺序执行更新
  • 其它副本向primary汇报操作情况
  • primary回复client操作情况,任何副本错误都导致此次请求失败,并且此时副本处于不一致状态(写操作完成情况不一样)。client会尝试几次3到7的步骤,实在不行就只能重头来过

如果一个写请求太大或者跨越chunk,GFS的client会将其拆分为多个写请求,每个写请求都遵循上述过程,但是可能和其它应用的写操作交叉在一起。所以这些写操作共享数据区域可能包含几个写请求的碎片(下文提到的undefined状态)

GFS提供另一种写操作append record,append只在文件尾部以record为单位(为了避免内部碎片,record一般不会很大)写入数据。append是原子性的,GFS保证将数据顺序地写到文件尾部至少一次。appendrecord的流程和图2类似,只是在primary有点区别,GFS必须保证一个record存储在一个chunk内,所以当primary判断当前chunk无足够空间时,通知所有副本将chunk填充,然后汇报失败,client会申请创建新chunk并重新执行一次append record操作。如果该chunk大小合适,primary会将数据写到数据块的尾部,然后通知其它副本将数据写到一样的偏移。任何副本append失败,各个副本会处于不一致状态(完成或未完成),primary必然是成功写入的。客户端重试append record操作时,因为primary的chunk长度已经变化,primary必须在新的偏移写入数据,而其它副本也是照做。导致上一个失败的偏移位置,各个副本处于不一致状态,应用必须自己区分record正确与否,称为无效数据区

表1说明了GFS的一致性保证,明白write和append操作后就容易理解了。consistent指的是多个副本之间是完全一致的;defined指的是副本数据修改后不仅一致而且client能看到其修改的数据全貌

  • 成功的连续write是已定义的,各个副本数据一致
  • 成功的并发write能保证一致性,各个副本是一样的,但数据并不一定如用户所期望,多个用户修改可能交错在一起
  • 失败的write操作,使副本之间不一致,且数据undefined,不同client可能看到不同数据(区别defined、undefined数据方法)
  • 成功的append操作,不管是顺序还是并发都是defined,因为GFS保证append是原子性的。有效数据区确实是defined的,但是失败append操作留下的无效数据区可能会有不一致的情况,所以中间可能零散分布着不一致的数据

如上所述,primary协调下,能保证并发write的一致性。但还有一些可能会导致数据不一致,比如chunkserver宕机错过了数据更新,这时会出现新旧版本的数据,GFS为每个数据块分配版本解决这个问题。master每次授权数据块租约给primary之前,都会增加数据块的版本号,并且通知所有副本更新版本号。客户端需要读数据时当然会根据这个版本号来判断副本的数据是否最新

  1. 可用性

为了保证数据的可用性,GFS为每个数据块存储了多个副本,主要关注下元数据的可用性。

GFS是典型的集中式元数据模型,一个元数据服务器承担了巨大的压力,不仅有性能问题,还有单点故障问题。master为了能快速从故障中恢复过来,采用了log和checkpoint技术,log记录了元数据的每一次变化。checkpoint相当于一次全量备份,log相当于连续数据保护,master宕机后,先恢复到checkpoint,然后根据log恢复到最新状态。每次创建一个新的checkpoint,log可以清空,有效控制了log的大小。

如果master完全坏了怎么办?GFS设置“影子”服务器,master将日志备份到影子,影子按照日志把元数据与master同步。正常工作时,影子可以分担一部分元数据访问工作,当然不提供直接的写操作


0 0
原创粉丝点击