转载请注明:http://duanple.blog.163.com/blog/static/7097176720109151534289/ 作者 phylips@bmy

 6.测量

在这一节,我们用一些小规模的测试来展示GFS架构和实现固有的一些瓶颈,有一些数字来源于google的实际集群。

 6.1小规模测试

我们在一个由一个master,两个master备份,16个chunkserver,16个client组成的GFS集群上进行了性能测量。这个配置是为了方便测试,实际中的集群通常会有数百个chunkserver,数百个client。

 所有机器的配置是,双核PIII 1.4GHz处理器,2GB内存,两个80G,5400rpm硬盘,以及100Mbps全双工以太网连接到HP2524交换机。所有19个GFS服务器连接在一个交换机,所有16个客户端连接在另一个上。两个交换机用1Gbps的线路连接。

 6.1.1读操作

N个客户端从文件系统中并发读。每个客户端在一个320GB的文件集合里随机4MB进行读取。然后重复256次,这样每个客户端实际上读取了1GB数据。Chunkserver总共只有32GB内存,因此我们估计在linux的buffer cache里最多有10%的命中率。我们的结果应该很接近一个几乎无缓存的结果。 

 

【google论文二】Google文件系统(下) - 星星 - 银河里的星星

 

图3(a)展示了对于N个客户端的总的读取速率以及它的理论上的极限。当2个交换机通过一个1Gbps的链路连接时,它的极限峰值是125MB/s,客户端通过100Mbps连接,那么换成单个客户端的极限就是12.5MB/s。当只有一个客户端在读取时,观察到的读取速率是10MB/s,达到了单个客户端极限的80%。当16个读取者时,总的读取速率的94 MB/s,大概达到了链路极限(125MB/s)的75%,换成单个客户端就是6 MB/s。效率从80%降到了75%,是因为伴随着读取者的增加,多个读者从同一个chunkserver并发读数据的概率也随之变大。

6.1.2写操作

N个客户端并行向N个不同的文件写数据。每个客户端以1MB的单个写操作总共向一个新文件写入1GB数据。总的写速率以及它的理论上的极限如图3(b)所示。极限值变成了67 MB/s,是因为我们需要将每个字节写入到16个chunkserver中的3个,每个具有12.5MB/s的输入连接。

 单个客户端的写入速率是6.3 MB/s,大概是极限值的一半。主要原因是我们的网络协议栈。它不能充分利用我们用于chunk副本数据推送的流水线模式。将数据从一个副本传递到另一个副本的延迟降低了整体的写速率。

 对于16个客户端,总体的写入速率达到了35 MB/s,平均每个客户端2.2 MB/s,大概是理论极限的一半。与写操作类似,伴随着写者的增加,多个写者从同一个chunkserver并发写数据的概率也随之变大。另外对于16个写者比16个读者更容易产生碰撞,因为每个写者将关联到3个不同的副本。

 写者比我们期望的要慢。在实际中,这还末变成一个主要问题,因为尽管它可能增加单个客户端的延时,但是当系统面对大量客户端时,其总的写入带宽并没有显著的影响。

 6.1.3记录追加

图3(c)展示了record append的性能。N个客户端向单个文件并行的append。性能取决于保存了该文件最后那个chunk的那些chunkserver,与客户端的数目无关。当只有一个客户端时,能达到6.0MB/s,当有16个客户端时就降到了4.8 MB/s。主要是由于拥塞以及不同的客户端的网络传输速率不同造成的。

 我们的应用程序倾向于并行创建多个这样的文件。换句话说,N个客户端向M个共享文件并行append,在这里N和M通常是几十甚至几百大小。因此在我们的实验中出现的chunkserver的网络拥塞问题在实际中并不是一个显著的问题,因为当一个文件的chunkserver比较繁忙的时候,它可以去写另一个。

6.2现实的集群

我们选择在google内部使用的两个集群进行测试作为相似的那些集群的一个代表。集群A主要用于100多个工程的日常研发。它会从数TB的数据中读取数MB的数据,对这些数据进行转化或者分析,然后将结果再写回集群。集群B主要用于产品数据处理。它上面的任务持续时间更长,持续地在生成和处理数TB的数据集合,只是偶尔可能需要人为的参与。在这两种情况下,任务都是由分布在多个机器上的很进程组成,它们并行的读写很多文件。 

6.2.1存储

【google论文二】Google文件系统(下) - 星星 - 银河里的星星

 正如表中前5个字段所展示的,两个集群都有数百个chunkserver,支持TB级的硬盘空间,空间已经被充分使用但还没全满。已用的空间包含chunk的所有副本。通常文件存在三个副本,因此这两个集群实际分别存储了18TB和52TB的数据。 

这两个集群的文件数目很接近,尽管B集群有大量的死文件(那些已经被删除或者被新版本文件所替换但空间还没有被释放的文件)。而且它具有更多的trunk,因为它上面的文件通常更大。

 6.2.2元数据

所有的Chunkserver总共存储了数十G的元数据,大部分是用户数据的64kb块的校验和。Chunkserver上唯一的其他的元数据就是4.5节讨论的chunk的版本号。

 保存在master上的元数据要更小一些,只有数十MB,平均下来每个文件只有100来个字节。这也刚好符合我们的master的内存不会成为实际中系统容量限制的料想。每个文件的元数据主要是以前缀压缩格式存储的文件名称。还有一些其他的元数据比如文件所有者,权限,文件到chunk的映射以及chunk的当前版本。另外对于每个chunk我们还存储了当前的副本位置以及用于实现写时复制的引用计数。

 每个独立的server(chunkserver和master)只有50-100MB的元数据。因此,恢复是很快的:在server可以应答查询前只需要花几秒钟的时间就可以把它们从硬盘上读出来。然而,master的启动可能要慢一些,通常还需要30-60秒从所有的chunkserver获得chunk的位置信息。

 6.2.3读写速率

 

【google论文二】Google文件系统(下) - 星星 - 银河里的星星

 表3展示了不同时期的读写速率。进行这些测量时,两个集群都已经运行了大约一周(为了更新到最新版本的GFS,这两个集群被重启过)。

从启动开始看,平均写速率小于30MB/s。当我们进行这些测量时,集群B正在以100MB/s的速率进行密集的写操作,同时产生了300MB/s的网络负载,因为写操作将会传给3个副本。

 读速率要远高于写速率。正如我们料想的那样,整个工作负载组成中,多要多于写。这两个集群都处在繁重的读活动中。尤其是,A已经在过去的一个星期中维持了580MB/s的读速率。它的网络配置可以支持750MB/s,因此它已经充分利用了资源。B集群可支持1300 MB/s的峰值读速率,但是应用只使用了380 MB/s。

 6.2.4 master负载

表3也表明发送给master的操作速率大概是每秒200-500个操作。Master可以轻易的处理这个级别的速率,因此对于这些工作负载来说,它不会成为瓶颈。

 在早期版本的GFS中,master偶尔会成为某些工作负载的瓶颈。为了查找文件,花费大量的时间在巨大的目录(包含上千万的文件)中进行线性扫描。因此,我们改变了master的数据结构,使之可以在名字空间内进行有效的二分搜索。现在它可以简单的支持每秒上千次的文件访问。如果必要的话,我们还可以进一步的在名字空间数据结构前端提供名字查找缓存。

 6.2.5 恢复时间

一台Chunkserver失败后,它上面的那些chunk的副本数就会降低,必须进行clone以维持正常的副本数。恢复这些chunk的时间取决于资源的数量。在一个实验中,我们关闭集群B中的一个chunkserver。该chunkserver大概有15000个chunk,总共600GB的数据。为减少对于应用程序的影响以及为调度决策提供余地,我们的默认参数设置将集群的并发clone操作限制在91个(占chunkserver个数的40%),同时每个clone操作最多可以消耗6.25MB/s(50Mbps)。所有的chunk在23.2分钟内被恢复,备份速率是440MB/s。

 在另一个实验中,我们关掉了两个chunkserver,每个具有16000个chunk,660GB的数据。这次失败使得266个chunk降低到了一个副本,但是两分钟内,它们就恢复到了至少2个副本,这样就让集群能够容忍另一个chunkserver发生失败,而不产生数据丢失。

 6.3 工作负载剖析

在这一节,我们将继续在两个新的集群上对工作负载进行细致的对比分析。集群X是用于研究开发的,集群Y是用于产品数据处理。

6.3.1 方法和说明

这些结果只包含了客户端产生的请求,因此它们反映了应用程序的对整个文件系统的工作负载。并不包含为了执行客户端的请求进行的server间的请求,或者是内部的后台活动,比如写推送或者是重平衡。

 对于IO操作的统计是从GFS的server的PRC请求日志中重新构建出来的。比如为了增加并行性,GFS客户端代码可能将一个读操作拆分为多个RPC请求,我们通过它们推断出原始请求。因为我们的访问模式高度的程式化,希望每个错误都可以出现在日志中。应用程序显式的记录可以提供更精确的数据,但是重新编译以及重启正在运行中的客户端在逻辑上是不可能这样做的。而且由于机器数很多,收集这些数据也会变得很笨重。

 需要注意的是,不能将我们的工作负载过于一般化。因为GFS和应用程序是由google完全控制的,应用程序都是针对GFS进行专门优化的,同时GFS也是专门为这些应用而设计的。这种相互的影响可能也存在于一般的文件系统及其应用程序中,但是这种影响可能并不像我们上面所描述的那样。

 6.3.2 chunkserver负载

【google论文二】Google文件系统(下) - 星星 - 银河里的星星

 表4展示了操作根据大小的分布。读操作的大小表现出双峰分布,小型读操作(小于64kb)来自于那些在大量文件中查找小片数据的随机读客户端,大型读操作(超过512kb)来自于穿越整个文件的线性读操作。

集群Y中大量的读操作没有返回数据。我们应用程序,尤其是在产品系统中,经常使用文件作为生产者消费者队列。生产者并行的往文件中append数据,而消费者则从文件尾部读数据。有时候,如果消费者超过了生产者,就没有数据返回。集群X很少出现这种情况,因为它主要是用来进行短期数据分析,而不是长期的分布式应用。

 写操作的大小也表现出双峰分布。大型的写操作(超过256KB)通常来自于写操作者的缓冲。那些缓冲更少数据的写操作者,检查点或者经常性的同步或者简单的数据生成组成了小型的写操作(低于64KB)。

 对于记录的append,Y集群比X集群可以看到更大的大record append比率。因为使用Y集群的产品系统,针对GFS进行了更多的优化。

【google论文二】Google文件系统(下) - 星星 - 银河里的星星 

 表5展示了不同大小的数据传输总量。对于各种操作来说,大型的操作(超过256KB)构成了大部分的数据传输。但是小型(低于64KB)的读操作虽然传输了比较少的数据但是在数据读中也占据了相当的一部分,主要是由于随机seek造成的。

6.3.4 append与write

记录append操作被大量的应用尤其是在我们的产品系统中。对于集群X来说,按字节传输来算,write与append的比例是108:1,根据操作数来算它们的比例是8:1。对于集群Y,比例变成了3.7:1和2.5:1。对于这两个集群来说,它们的append操作都要比write操作大一些{操作数的比要远大于字节数的比,说明单个的append操作的字节数要大于write}。对于集群X来说,在测量期间的记录append操作要低一些,这可能是由其中具有特殊缓冲大小设置的应用程序造成的。

 正如期望的,我们的数据变更操作处于支配地位的是追加而不是重写{write也可能是追加}。我们测量了在主副本上的数据重写数量。对于集群X来说,以字节大小计算的话重写大概占了整个数据变更的0.0001%,以操作个数计算,大概小于0.0003%。对于Y集群来说,这两个数字都是0.05%,尽管这也不算大,但是还是要高于我们的期望。结果显示,大部分的重写是由于错误或者超时导致的客户端重写而产生的。它们并不是工作负载的一部分,而是属于重试机制。

 6.3.4 master负载

【google论文二】Google文件系统(下) - 星星 - 银河里的星星

 表6展示了对于master各种请求类型的剖析。大部分请求是为了得到chunk位置以及数据变更需要的租约持有信息。

 可以看到集群X和Y在delete请求上的限制区别,因为集群Y上存储的产品信息会周期性地生成被新版本数据所替换。这些不同被隐藏在open请求中,因为老版的数据在被写的时候的打开操作中被隐式的删除(类似与Unix的”w”打开模式)。

 查找匹配文件是一个类似于ls的模式匹配请求。不像其他的请求,它可能需要处理很大部分的名字空间,因此可能是很昂贵的。在集群Y上可以更频繁地看到它,因为自动化的数据处理任务为了了解整个应用程序的状态可能需要检查文件系统中的某些部分。与此相比,集群X需要更多显式的用户控制而且已经提前知道所需要的文件的名称。

7.经验

在构建和部署GFS的过程中,我们总结出了很多经验,观点和技术。

 起初,GFS只是考虑作为我们产品系统的后端文件系统。随着时间的推移,开始在研究和开发中使用。一开始它基本不支持像权限,quota这些东西,但是现在它们都已经有了。产品系统是很容易控制的,但是用户却不是。因此需要更多的设施来避免用户间的干扰。

 我们最大的问题是硬盘和linux相关性。我们的很多硬盘声称支持各种IDE协议版本的linux驱动,但是实际上它们只能在最近的一些上才能可靠的工作。因此如果协议版本如果相差不大,硬盘大多数情况下都可以工作,但是有时候这种不一致会使得驱动和内核在硬盘状态上产生分歧。由于内核的问题,这将会导致数据被默默的污染。这个问题使得我们使用校验和来检测数据污染,如果出现这种情况,我们就需要修改内核来处理这种协议不一致的情况。

之前,由于linux2.2内核的fsync()的花费,我们也碰到过一些问题。它的花费是与文件大小而不是被修改部分的大小相关的。这对于我们大的操作日志会是一个问题,尤其是在我们实现检查点之前。我们通过改用同步写来绕过了这个问题,最后迁移到Linux2.4来解决了它。

 另一个由于linux产生的问题是与读写锁相关的。在一个地址空间里的线程在从硬盘中读页数据(读锁)或者在mmap调用中修改地址空间(写锁)的时候,必须持有一个读写锁。在系统负载很高,产生资源瓶颈或者出现硬件失败时,我们碰到了瞬态的超时。最后,我们发现当磁盘读写线程处理前面映射的数据时,这个锁阻塞了网络线程将新的数据映射到内存。由于我们的工作瓶颈主要是在网络带宽而不是内存带宽,因此我们通过使用pread()加上额外的开销替代mmap()绕过了这个问题。

 尽管出现了一些问题,linux代码的可用性帮助了我们探索和理解系统的行为。在适当的时机,我们也会改进内核并与开源社区共享这些变化。

 8.相关工作

像其他的大型分布式文件系统比如AFS,GFS提供了一个本地的独立名字空间,使得数据可以为了容错或者负载平衡而透明的移动。但与AFS不同的是,为了提升整体的性能和容错能力,GFS将文件数据在多个存储服务器上存储,这点更类似于xFS或者Swift。

 硬盘是相对便宜的,而且与复杂的RAID策略相比,副本策略更简单。由于GFS完全采用副本策略进行冗余因此它会比xFS或者Swift消耗更多的原始存储。

与AFS,xFS,Frangipani,Intermezzo这些系统相比,GFS在文件系统接口下并不提供任何缓存。我们的目标工作负载类型对于通常的单应用程序运行模式来说,基本上是不可重用的,因为这种模式通常需要读取大量数据集合或者在里面进行随机的seek,而每次只读少量的数据。

一些分布式文件系统比如xFS,Frangipani,Minnesota’s GFS和GPFS删除了中央服务节点,依赖于分布式的算法进行一致性和管理。我们选择中央化测量是为了简化设计增加可靠性,获取灵活性。尤其是,一个中央化的master更容易实现复杂的chunk放置和备份策略,因为master具有大部分的相关信息以及控制了它们的改变。我们通过让master状态很小以及在其他机器上进行备份来解决容错。当前通过影子master机制提供可扩展性和可用性。对于master状态的更新,通过append到write-ahead 日志里进行持久化。因此我们可以通过类似于Harp里的主copy模式来提供一个比我们当前模式具有更强一致性的高可用性。

 我们未来将解决类似于Lustre的一个问题:大量客户端的整体性能。然而我们通过专注于我们自己的需求而不是构建一个POSIX兼容文件系统来简化了这个问题。另外,GFS加速不可靠组件的数量是很大的,因此容错是我们设计的中心。

 GFS很类似于NASD架构。但是NASD是基于网络连接的硬盘驱动器,GFS则使用普通机器作为chunkserver。与NASD不同,chunkserver在需要时分配固定大小的chunk,而没有使用变长对象。此外,GFS还实现了诸如重平衡,副本,产品环境需要的快速恢复。

 不像Minnesota’s GFS和NASD,我们并没有寻求改变存储设备的模型。我们更专注于解决使用现有商品化组件组成的复杂分布式系统的日常的数据处理需求。

 通过在生产者消费者队列中使用原子record append操作解决了与分布式操作系统River的类似问题。River使用基于内存的跨机器分布式队列以及小心的数据流控制来解决这个问题,而GFS只使用了一个可以被很多生产者append数据的文件。River模型支持m to n的分布式队列,但是缺乏容错,GFS目前只支持m to 1。多个消费者可以读取相同文件,但是它们必须协调好对输入负载进行划分(各自处理不相交的一部分)。

 9.总结

GFS包含了那些在商品化硬件上支持大规模数据处理的必要特征。尽管某些设计决定与我们特殊的应用类型相关,但是可以应用在具有类似需求和特征的数据处理任务中。

 针对我们当前的应用负载类型,我们重新审视传统的文件系统的一些假设。我们的审视,使得我们的设计中产生了一些与之根本不同的观点。我们将组件失败看做常态而不是异常,为经常进行的在大文件上的append进行优化,然后是读(通常是顺序的),为了改进整个系统我们扩展并且放松了标准文件系统接口。

 我们的系统通过监控,备份关键数据,快速和自动恢复来提供容错。Chunk备份使得我们可以容忍chunkserver的失败。这些经常性的失败,驱动了一个优雅的在线修复机制的产生,它周期性地透明的进行修复尽快的恢复那些丢失的副本。另外,我们通过使用校验和来检测数据损坏,当系统中硬盘数目很大的时候,这种损坏变得很正常。

 我们的设计实现了对于很多执行大量任务的并发读者和写者的高吞吐率。通过从数据传输中分离文件系统控制,我们来实现这个目标,让master来处理文件系统控制,数据传输则直接在chunkserver和客户端之间进行。通过增大chunk的大小以及chunk的租约机制,降低了master在普通操作中的参与。这使中央的master不会成为瓶颈。我们相信在当前网络协议栈上的改进将会提供客户端写出速率的限制。

 GFS成功地满足了我们的存储需求,同时除了作为产品数据处理平台外,还作为研发的存储平台而被广泛使用。它是一个使我们可以持续创新以及面对整个web的海量数据挑战的重要工具。

致谢