SKLCC’s 藏经阁

We are SKLCCers, we share our knowledge with each other.

GFS简介

| Comments

From Andy at SuZhou sklcc.com

Overview

  • 0x01 背景和简介
  • 0x02 逻辑结构与基本概念
  • 0x03 读、写、删、HA
  • 0x04 其他细节
  • 0x05 测试数据
  • 0x06 结束语

背景和简介

GFS即Google File System,出自Google在SOSP(2003)的一篇论文《The Google File System》,为Google三宝之一,其余还有Map Reduce和Big Table,当前很火的Hadoop系统中的HDFS、MapReduce和HBase就是其开源实现。由于Hadoop离线批处理的性质以及其他缺点,Google于2014年6月发布其Google Data Cloud服务,该服务支持实时操作和更复杂的逻辑处理,并且效率也有提升。据说在Google公司内部,之后的代码都运行在这个新平台上。Spark为其开源实现。本质上说Spark仍旧是MapReduce和批处理,只是分的更小更细。另外,大数据的实时处理系统还有Storm。

GFS在设计和实现上个人感觉是针对MapReduce的,所以和其他的分布式文件系统有一定的差异,对于小伙伴们有一定的参考意义,但不建议直接照搬。GFS在设计上有如下几点考虑:

  1. 由大量普通PC机组成,单点的故障失效是常态而不是异常。
  2. 客户机数量庞大。
  3. 文件数量多且单个文件大,通常在100MB以上,GB级别是常态。经常需要处理快速增长的、并且由数亿个对象构成的、数以TB的数据集
  4. 大量的操作为在文件后追加数据,几乎没有随机写入,写完后只读,且读取方式基本上只有大规模顺序读和小规模随机读
  5. 网络带宽稳定高效

所以这决定了在GFS中持续监控、错误检测、灾难处理、自动恢复机制是必须的。并且需要支持客户机的并行追加写入操作(开销要尽可能小),Block的size也必须要重新考虑,文件缓存也是没有必要的(流读取+存不下)

BTW:GFS是在linxu原有文件系统上又封了一层,并不是和ext4、ntfs等文件系统同一级别的。

逻辑结构与基本概念

GFS结构其实很简单,主要是一个Master(逻辑的)和很多Chunk Server(物理的)组成。下图表示了GFS一次写操作的流程

GFS中主要的基本概念有:

  • Applications:正常的应用
  • GFS Client:GFS的客户端,通过GFS提供的API向GFS读取或写入数据
  • GFS Master:GFS的主控结点(逻辑),一般一个GFS集群只包含一个逻辑的Master,上面存着GFS中各种元数据(metadata),主要有三种,包括:文件和Chunk的命名空间、文件和Chunk的对应关系、每个Chunk副本的存放地点。另外还有文件的所有者、权限、每个chunk的版本等信息。数据采用前缀压缩,并存在内存中
  • Chunk:翻译成中文为“大块”,GFS操作的主要单元,默认大小为64MB,采用惰性空间分配策略(真正需要的时候再分配空间,如果没有用到这个对象,可以减少分配,从而加快响应速度)
  • Chunk Server:存放chunk的物理主机
  • Chunk handle:Master给每一个chunk分配的不变的唯一的64bit的编号
  • Namespace:一个全路径和元数据的映射表,采用树形结构和前缀压缩
  • Checkpoint:对系统状态的snapshot行为。GFS使用日志来记录之前所有的操作,通过cp(checkpoint)可以大大减少历史日志的数量(因为cp之后历史日志就可以删除了),并且通过最近的cp和有限的日志就可以快速恢复GFS。通常会保留几个历史cp
  • Snapshot:快照操作几乎可以瞬间完成对一个文件或者目录树(“源”)做一个拷贝,并且几乎不会对正在进行的其它操作造成任何干扰。我们的用户可以使用快照迅速的创建 一个巨大的数据集的分支拷贝(而且经常是递归的拷贝拷贝),或者是在做实验性的数据操作之前,使用快照操作备份当前状态,这样之后就可以轻松的提交或者回滚到备份时的状态
  • 一致的:如果所有客户端,无论从哪个副本读取,读到的数据都一样,那么我们认为文件修改是“一致的”
  • 已定义的:如果对文件的数据修改之后,文件修改的部分是一致的,并且客户端能够看到写入操作全部的内容,那么这个文件修改的部分是“已定义的”
  • Reader和Writer:分别表示执行GFS读取和写入操作的程序
  • lease(租约),GFS通过lease来保证多个副本之间的一致性,当我们对一个chunk进行追加操作时,Master为chunk的一个副本建立一个lease,这个副本被称之为主chunk(primary chunk),主chunk对chunk所有的更改操作进行序列化,虽有的副本都会按照这个序列进行修改操作。

Ps:chunk size为64MB这么大的原因是设置大的Block size可以减少通信频率,client在较长时间内都是对一个chunk进行操作,从而只需要与chunk server维持一条TCP链接即可,可以降低网路负载,另外还可以降低master要保存的metadata量。当然,大的size也会有缺点,当多个client同时访问小文件时(例如小于64MB的文件),文件所在的chunk server和它副本所在的chunk server会成为热点,导致有可能局部过载。解决方法的话可以增加小文件的副本数,或者可以考虑允许client从其他client处获取该文件。

读、写、删、HA

Applications通过GFS client向GFS提交一个读请求,Client会将文件名(file name)(个人觉得是文件的全路径)和chunk index(通过程序指定的字节偏移和固定的chunk size可以计算出chunk的偏移,个人觉得应该叫chunk offset更合适)发送给GFS Master,Master通过file name在namespace中找到相应的metadata,metadata中包含有文件对应chunk的chunk handle和所在在的chunk server的位置,然后Master会把chunk handle和chunk locations(包括所有副本的位置)返回给client。由于考虑到时间局限性(同一个位置在一段时间内会被频繁操作)和空间局限性(一段空间有可能会被频繁访问),client会缓存Master回复的metadata,Master也会额外返回其他相邻的chunk的metadata。然后GFS Client会直接去找chunk server(在所有副本中选一个就行了,一般会选择最近的)请求数据,请求信息包含了chunk handle和byte range。chunk server返回具体的数据。在后续的读取操作中,client不必再和master交互了,直接向chunk server要数据,除非client上缓存的metadata过期了,或者文件被重新打开了(也就是说,继续read没有问题,但是如果重新open文件之后再read就要再去和master谈谈了)。

写(并行写)

在GFS的写操作有两种,一是单个client顺序写,二是多客户端的并行写,另外这里的写指都是追加操作,个人感觉这样的需求定义是来源于MapReduce中的Map操作产生的中间数据。

对于第一种情况,应用程序从头到尾写入数据,生成了一个文件。写入所有数据之后,应用程序自动将文件改名为一个永久保存的文件名,或者周期性的作Checkpoint,记录成功写入了多少数据。Checkpoint文件可以包含程序级别的checksum。Readers仅校验并处理上个Checkpoint之后产生的文件修改,这些文件修改的状态一定是已定义的。这个方法满足了我们一致性和并发处理的要求。追加写入比随机位置写入更加有效率,对应用程序的失败处理更具有弹性。Checkpoint可以让Writer以渐进的方式重新开始,并且可以防止Reader处理已经被成功写入,但是从应用程序的角度来看还并未完成的数据。

对于第二种情况,许多应用程序并行的追加数据到同一个文件,比如进行结果的合并或者是一个生产者-消费者队列。记录追加方式的“至少一次追加”的特性保证了Writer的输出。Readers使用下面的方法来处理偶然性的填充数据和重复内容。Writers在每条写入的记录中都包含了额外的信息,例如Checksum(不一定是Checksum,这里说checksum只是举例而已),用来验证它的有效性。Reader可以利用Checksum识别和抛弃额外的填充数据和记录片段。如果应用不能容忍偶尔的重复内容(比如,如果这些重复数据触发了非幂等操作),可以用记录的唯一标识符来过滤它们。简单的说就是GFS保证每个client的写操作都会追加成功至少一次,各个client的追加数据的顺序并不保证,并且也有可能重复追加。当然,GFS也保证了各个client追加的数据不会交叉追加在文件尾部。

在实际的写操作中,GFS通过lease来保证一个chunk所有的副本的一致性。具体流程为:

  1. client向Master存文哪一个chunk有当前的lease,和它的副本位置,如果没有一个chunk有lease,Master会选择其中一个副本与之建立lease。

  2. Master结点将主chunk的chunk handle和其他副本(又叫secondary副本或者二级副本)的位置返回给client,client缓存这些信息,如果主chunk不可用或者主chunk表明它已不再有lease的时候,client需要重新和master联系。

  3. client把数据推送到所有的副本上(包括主chunk和所有的二级副本),推送没有顺序规定,可以任意顺序推送。一般情况下为了充分利用每台机器的带宽,数据是沿着chunk服务器链顺序推送的,而不是以其他拓扑形式分散推送(例如树型拓扑结构),在线性推送模式下,每台机器的出口带宽都用最快的速度传输数据,而不是在多个接受者之间分配带宽。为了尽可能的避免出现网络瓶颈和高延迟的链接(eg,inter-switch最有可能出现类似问题),每台机器都尽量的在网络拓扑中选择一台还没有接收到数据的、离自己最近的机器作为目标推送数据。假设客户机把数据从Chunk服务器S1推送到S4。它把数据推送到最近的Chunk服务器S1。S1把数据推送到S2,因为S2和S4中最接近的机器是S2。同样的,S2把数据传递给S3和S4之间更近的机器,依次类推推送下去。我们的网络拓扑非常简单,通过IP地址就可以计算出节点的“距离”。

  4. 当所有的副本都确认接受到数据,client发送写请求到主chunk所在的chunk server,这个请求中包含之前推送的到所有的副本信息,主chunk为接收到的所有操作分配顺序的序列号,这些操作可能来 自不同的客户机,序列号保证了操作顺序执行。它顺序执行这些操作,并更新自己的状态。

  5. 主chunk把写请求传递给所有的二级副本,每个二级副本按照序列号以相同的顺序执行这些操作。

  6. 所有的二级副本回复主chunk,它们完成了这些操作。

  7. 主chunk所在的server回复client。在这个过程中,任何出错都会返回给client,如果操作在主chunk上失败了,操作就不会被分配序列号,也不会被通知二级副本。如果client的请求被确认为失败的话,client的会从第3步到第7步重新尝试几次。

BTW:如果一个写操作很大,大于64M,或者跨越多个chunk,GFS client会把它分为多个写操作分次执行。

当一个文件被应用程序删除时,Master节点象对待其它修改操作一样,立刻把删除操作以日志的方式记录下来。但是,Master节点并不马上回收资源,而是把文件名改为一个包含删除时间戳的、隐藏的名字。当Master节点对文件系统命名空间做常规扫描的时候,它会删除所有三天前的隐藏文件(这个时间间隔是可以设置的)。直到文件被真正删除,它们仍旧可以用新的特殊的名字读取,也可以通过把隐藏文件改名为正常显示的文件名的方式“反删除”。当隐藏文件被从名称空间中删除,Master服务器内存中保存的这个文件的相关元数据才会被删除。

HA

由于在GFS的设定中PC的单点故障是常态而不是异常,所以GFS的HA是相当必要的。在整个GFS中,通过两条简单的策略来保证GFS的高可用性:快速恢复和复制(包括chunk的复制和Master的复制)

快速恢复,Master和Chunk server都被设计为是可快速恢复的(对于这里的Master和Chunk server可以理解为两种deamon),它们都能在数秒钟内恢复之前的状态并且重新启动。通常无论服务是否正常,都是通过kill来关闭服务器的(这里的服务器泛指Master和Chunk守护进程)。他们的秒级快速恢复是由于对他们的复制操作来保证的(个人理解)

复制,对于Chunk,每一个chunk都会被复制到不同的chunk服务器上,用户可以为不同的namespace设定不同的复制级别,默认是3.当有chunk离线,或者通过checksum发现数据已损坏,Master节点通过clone已有的副本来保证每个chunk的完整性。对于Master,Master服务器所有的操作日志和CP(checkpoint)都会被复制到多台机器上,所有对Master的操作都是在其操作日志成功写入到Master服务器备节点和本机磁盘之后才完成的。GFS系统外部的监控进程会保证当Master进程失效之后再其他的存有完整操作日志的机器上启动一个新的Master进程。

其他细节

  • Master不会持久保存Chunk的位置信息,只有在Master启动时,或者在有新的Chunk server加入是才会想各个Chunk server轮询它们锁存储的Chunk的信息。
  • Master与Chunk server之间会有心跳协议,master对chunk server的一些指令是也是包含在心跳协议中的
  • Master会将所有的metadata都存在内存中,由于Chunk zise够大,所以不是一个严重的问题,但当集群规模更大时,需要增加Master的内存容量
  • 当一个chunk所有的副本都损坏时,这个chunk才不可逆转的丢失了,这是application会收到明确的错误信息,而不是损坏的数据
  • Master通过保存每个chunk的version来区分当前副本和过期副本。无论何时,只要Master节点和Chunk签订一个新的租约,它就增加Chunk的版本号,然后通知最新的副本。Master节点和这些副本都把新的版本号记录在它们持久化存储的状态信息中。这个动作发生在任何客户机得到通知以前,因此也是对这个Chunk开始写之前。如果某个副本所在的Chunk服务器正好处于失效状态,那么副本的版本号就不会被增加。Master节点在这个Chunk服务器重新启动,并且向Master节点报告它拥有的Chunk的 集合以及相应的版本号的时候,就会检测出它包含过期的Chunk。如果Master节点看到一个比它记录的版本号更高的版本号,Master节点会认为它 和Chunk服务器签订租约的操作失败了,因此会选择更高的版本号作为当前的版本号。Master节点在例行的垃圾回收过程中移除所有的过期失效副本。在此之前,Master节点在回复客户机的Chunk信息请求的时候,简单的认为那些过 期的块根本就不存在。另外一重保障措施是,Master节点在通知客户机哪个Chunk服务器持有租约、或者指示Chunk服务器从哪个Chunk服务器进行克隆时,消息中都附带了Chunk的版本号。客户机或者Chunk服务器在执行操作时都会验证版本号以确保总是访问当前版本的数据。
  • 在Chunk服务器空闲的时候,Master会扫描和校验每个不活动的Chunk的内容。这使得我们能够发现很少被读取的Chunk是否完整。一旦发现有Chunk 的数据损坏,Master可以创建一个新的、正确的副本,然后把损坏的副本删除掉。

测试数据

机器配置:两个 PIII 1.4GHz 处理器, 2GB 内存,两个 80G/5400rpm 的硬盘,以及100Mbps 全双工以太网连接到一个 HP2524 交换机。

GFS集群中所有的19台服务器都连接在一个交换机,所有16台客户机连接到另一个交换机上。两个交换机之间使用1Gbps的线路连接。

N 个客户机从 GFS 文件系统同步读取数据。每个客户机从 320GB 的文件集合中随机读取 4MB 的内容。读取操作重复执行 256 次,因此,每个客户机最终都读取 1GB 的数据。所有的 Chunk 服务器加起来总共只有 32GB 的内存,因此, 我们预期只有最多 10% 的读取请求命中 Linux 的文件系统缓冲。我们的测试结果应该和一个在没有文件系统缓存的情况下读取测试的结果接近。

读取

图 ( a )显示了 N 个客户机整体的读取速度以及这个速度的理论极限。当连接两个交换机的 1Gbps 的链路饱和时,整体读取速度达到理论的极限值是 125MB/S ,或者说每个客户机配置的 100Mbps 网卡达到饱和时,每个客户机读取速度的理论极限值是 12.5MB/s 。实测结果是,当一个客户机读 取的时候,读取的速度是 10MB/s ,也就是说达到客户机理论读取速度极限值的 80% 。对于 16 个客户机,整体的读取速度达到了 94MB/s ,大约是理论整体读取速度极限值的 75% ,也就是说每个客户机的读取速度是6MB/s 。读取效率从 80% 降低到了 75% ,主要的原因是当读取的客户机增加时,多个客户机同时读取一个 Chunk 服务器的几率也增加了,导致整体的读取效率下降。

写入

N 个客户机同时向 N 个不同的文件中写入数据。每个客户机以每次 1MB 的速度连续写入 1GB 的数据。图 ( b )显示了整体的写入速度和它们理论上的极限值。 理论上的极限值是 67MB/s (12.5*16/3=67),因为我们需要把每一 byte 写入到 16 个 Chunk 服务器中的 3 个上,而每个 Chunk 服务器的输入连接速度是12.5MB/s 。一个客户机的写入速度是 6.3MB ,大概是理论极限值的一半。导致这个结果的主要原因是我们的网络协议栈。它与我们推送数据到 Chunk 服务器时采用的管道模式不相适应。从一个副本到另一个副本的数据传输延迟降低了整个的写入速度。16 个客户机整体的写入速度达到了 35MB/s (即每个客户机 2.2MB/s ),大约只是理论极限值的一半。和多个客户机读取的情形很类型,随着客户机数量的增加,多个客户机同时写入同一个 Chunk 服务器的几率也增加了。而且, 16 个客户机并行写入可能引起的冲突比 16 个客户机并行读取要大得多,因为每个写入都会涉及三个不同的副本。写入的速度比我们想象的要慢。在实际应用中,这没有成为我们的主要问题,因为即使在单个客户机上能够感受到延时,它也不会在有大量客户机的时候对整体的写入带宽造成显著的影响。

追加

图 ( c )显示了记录追加操作的性能。 N 个客户机同时追加数据到一个文件。记录追加操作的性能受限于保存文件最后一个 Chunk 的 Chunk 服务器的带宽,而与客户机的数量无关。记录追加的速度由一个客户机的 6.0MB/s 开始,下降到 16 个客户机的 4.8MB/s 为止,速度的下降主要是由于不同客户端的网络拥塞以及网络传输速度的不同而导致的。我们的程序倾向于同时处理多个这样的文件。换句话说,即 N 个客户机同时追加数据到 M 个共享文件中,这里 N 和 M 都是数十或者数百以上。所以,在我们的实际应用中, Chunk 服务器的网络拥塞并没有成为一个严重问题,如果 Chunk 服务器的某个文件正在写入,客户机会去写另外一个文件。

结束语

GFS是一个完整的系统,由于个人水平有限,没能很好的介绍清楚GFS的所有特性,需要深入了解的,还是去看GFS的原文。

另外,GFS很多设计要点都是针对google特殊的需要定制的,所以个人感觉了解即可,不必深究,但是其中很多的思想还是可以借鉴和学习的。

Comments