GFS文件系统剖析(中)一致性模型及读写流程介绍

阿凡达2018-07-04 13:33

前言

上一篇章:GFS文件系统剖析(上)-系统架构及设计要点中介绍了GFS的一些架构和设计要点。这篇文章重点谈谈关键的一致性模型设计以及关键的读写流程。

一、一致性模型

上一篇中提到了GFS的一致性模型要求放松了,是一个宽松的一致性模型,相对来说实现比较简单。下面具体来说说GFS的一致性保障机制是如何做的。

  • 1. GFS一致性保障机制 : 文件命名空间的修改(例如,文件创建)是原子性的。它们仅由Master节点的控制:命名空间锁提供了原子性和正确性(后续会介绍命名空间锁管理)的保障;Master节点的操作日志定义了这些操作在全局的顺序。 下面一张表展示了不同方式下数据修改(包含写入和追加)操作后文件region的状态变化:

这里有几个术语: region状态:表示文件中某一段范围的状态,这个状态是对于客户端来说的,不同的状态对客户端来说效果不一样。 一致状态:如果所有客户端无论从那个副本读取,读到的数据都一样,则认为文件region状态是一致的。 已定义状态:如果对文件的数据修改以后,region是一致的,并且客户端能看到所有写入操作的全部内容,则这个region是已定义的(隐含了一致性)。 一致但是未定义状态:并行修改操作成功完成之后,所有客户端看到了同样的数据,但是无法读到任何一次写入操作写入的数据,即数据是包含多个修改操作的,混杂的数据。这就叫一致但是未定义的。 不一致状态:不同的客户在不同的时间会看到不同的数据。 写入操作:写入操作把数据写在应用程序指定的文件偏移位置上。 追加操作:追加操作写的偏移位置是文件的尾部。

经过了一系列的成功的修改操作之后,GFS确保被修改的文件region是已定义的,并且包含最后一次修改操作写入的数据。GFS通过以下措施确保上述行为:(a) 对Chunk的所有副本的修改操作顺序一致,(b)使用Chunk的;版本号来检测副本是否因为它所在的Chunk服务器宕机而错过了修改操作而导致其失效。失效的副本不会再进行任何修改操作,Master服务器也不再返回这个Chunk副本的位置信息给客户端。它们会被垃圾收集系统尽快回收。

由于Chunk位置信息会被客户端缓存,所以在信息刷新前,客户端有可能从一个失效的副本读取了数据。在缓存的超时时间和文件下一次被打开的时间之间存在一个时间窗,文件再次被打开后会清除缓存中与该文件有关的所有Chunk位置信息。而且,由于我们的文件大多数都是只进行追加操作的,所以,一个失效的副本通常返回一个提前结束的Chunk而不是过期的数据。当一个Reader(读取进程)重新尝试并联络Master服务器时,它就会立刻得到最新的Chunk位置信息。 即使在修改操作成功执行很长时间之后,组件的失效也可能损坏或者删除数据。GFS通过Master服务器和所有Chunk服务器的定期“握手”来找到失效的Chunk服务器,并且使用Checksum来校验数据是否损坏。一旦发现问题,数据要尽快利用有效的副本进行恢复。只有当一个Chunk的所有副本在GFS检测到错误并采取应对措施之前全部丢失,这个Chunk才会不可逆转的丢失。在一般情况下GFS的反应时间(指Master节点检测到错误并采取应对措施)是几分钟。即使在这种情况下,Chunk也只是不可用了,而不是损坏了:应用程序会收到明确的错误信息而不是损坏的数据。

  • 2. 程序具体实现 : 使用GFS的应用程序可以利用一些简单技术实现这个宽松的一致性模型,这些技术也用来实现一些其它的目标功能,包括:尽量采用追加写入而不是覆盖,Checkpoint,自验证的写入操作,自标识的记录。 在实际应用中,GFS所有的应用程序对文件的写入操作都是尽量采用数据追加方式,而不是覆盖方式。一种典型的应用,应用程序从头到尾写入数据,生成了一个文件。写入所有数据之后,应用程序自动将文件改名为一个永久保存的文件名,或者周期性的作Checkpoint,记录成功写入了多少数据。Checkpoint文件可以包含程序级别的校验和。Readers仅校验并处理上个Checkpoint之后产生的文件region,这些文件region的状态一定是已定义的。这个方法满足了我们一致性和并发处理的要求。追加写入比随机位置写入更加有效率,对应用程序的失败处理更具有弹性。Checkpoint可以让Writer以渐进的方式重新开始,并且可以防止Reader处理已经被成功写入,但是从应用程序的角度来看还并未完成的数据。 再来分析另一种典型的应用。许多应用程序并行的追加数据到同一个文件,比如进行结果的合并或者是一个生产者-消费者队列。记录追加方式的“至少一次追加”的特性保证了Writer的输出。Readers使用下面的方法来处理偶然性的填充数据和重复内容。Writers在每条写入的记录中都包含了额外的信息,例如Checksum,用来验证它的有效性。Reader可以利用Checksum识别和抛弃额外的填充数据和记录片段。如果应用不能容忍偶尔的重复内容(比如,如果这些重复数据触发了非幂等操作),可以用记录的唯一标识符来过滤它们,这些唯一标识符通常用于命名程序中处理的实体对象,例如web文档。这些记录I/O功能(These functionalities for record I/O)(除了剔除重复数据)都包含在我们的程序共享的库中,并且适用于Google内部的其它的文件接口实现。所以,相同序列的记录,加上一些偶尔出现的重复数据,都被分发到Reader了。

二、读写流程介绍

讲流程之前需要现讲一下“租约”这个术语。 租约(lease)机制主要是为了保持多个副本间变更顺序一致性的。Master节点为Chunk的一个副本建立一个租约,我们把这个副本叫做主Chunk。主Chunk对Chunk的所有更改操作进行序列化。所有的副本都遵从这个序列进行修改操作。因此,修改操作全局的顺序首先由Master节点选择的租约的顺序决定,然后由租约中主Chunk分配的序列号决定。 设计租约机制的目的是为了最小化Master节点的管理负担。租约的初始超时设置为60秒。不过,只要Chunk被修改了,主Chunk就可以申请更长的租期,通常会得到Master节点的确认并收到租约延长的时间。这些租约延长请求和批准的信息通常都是附加在Master节点和Chunk服务器之间的心跳消息中来传递。有时Master节点会试图提前取消租约(例如,Master节点想取消在一个已经被改名的文件上的修改操作)。即使Master节点和主Chunk失去联系,它仍然可以安全地在旧的租约到期后和另外一个Chunk副本签订新的租约。 写入流程如下,例如有1个主副本,2个从副本的情况:

◆ 客户机向Master节点询问哪一个Chunk服务器持有当前的租约,以及其它副本的位置。如果没有一个Chunk持有租约,Master节点就选择其中一个副本建立一个租约(这个步骤在图上没有显示)。

◆ Master节点将主Chunk的标识符以及其它副本(又称为secondary副本、二级副本)的位置返回给客户机。客户机缓存这些数据以便后续的操作。只有在主Chunk不可用,或者主Chunk回复信息表明它已不再持有租约的时候,客户机才需要重新跟Master节点联系。

◆ 客户机把数据推送到所有的副本上。客户机可以以任意的顺序推送数据。Chunk服务器接收到数据并保存在它的内部LRU缓存中,一直到数据被使用或者过期交换出去。由于数据流的网络传输负载非常高,通过分离数据流和控制流,我们可以基于网络拓扑情况对数据流进行规划,提高系统性能,而不用去理会哪个Chunk服务器保存了主Chunk。

◆当所有的副本都确认接收到了数据,客户机发送写请求到主Chunk服务器。这个请求标识了早前推送到所有副本的数据。主Chunk为接收到的所有操作分配连续的序列号,这些操作可能来自不同的客户机,序列号保证了操作顺序执行。它以序列号的顺序把操作应用到它自己的本地状态中(也就是在本地执行这些操作,这句话按字面翻译有点费解,也许应该翻译为“它顺序执行这些操作,并更新自己的状态”)。

◆主Chunk把写请求传递到所有的二级副本。每个二级副本依照主Chunk分配的序列号以相同的顺序执行这些操作。

◆所有的二级副本回复主Chunk,它们已经完成了操作。

◆主Chunk服务器(即主Chunk所在的Chunk服务器)回复客户机。任何副本产生的任何错误都会返回给客户机。在出现错误的情况下,写入操作可能在主Chunk和一些二级副本执行成功。(如果操作在主Chunk上失败了,操作就不会被分配序列号,也不会被传递。)客户端的请求被确认为失败,被修改的region处于不一致的状态。我们的客户机代码通过重复执行失败的操作来处理这样的错误。在从头开始重复执行之前,客户机会先从步骤(3)到步骤(7)做几次尝试。

如果应用程序一次写入的数据量很大,或者数据跨越了多个Chunk,GFS客户端代码会把它们分成多个写操作。这些操作都遵循前面描述的控制流程,但是可能会被其它客户机上同时进行的操作打断或者覆盖。因此,共享的文件region的尾部可能包含来自不同客户机的数据片段,尽管如此,由于这些分解后的写入操作在所有的副本上都以相同的顺序执行完成,Chunk的所有副本都是一致的。这使文件region处于上一节描述的一致的、但是未定义的状态。 读取流程 可以再看一下设计架构图中的conrol 和data流。

  1. GFSclient将服务所要读取的文件名与byte offset,根据系统chunk大小,换算成文件的chunk index,即文件数据所处的第几个chunk。
  2. 将filename与chunk index传给master。
  3. Master返回给client元数据信息(包含chunk handle与实际存储的chunkserver location)。然后client获取到该信息,作为key值与filename+chunkindex缓存起来。
  4. Client根据这些元数据信息,直接对chunkserver发出读请求。对于三副本而言(一份chunk存储在三台不同的chunkserver),client选择离自己最近的chunkserver(网络?),通过之前获取的元数据信息找到需要读的chunk位置以及下一个chunk位置。如果缓存的元数据信息已过期,则需要重新向master去获取一遍。
  5. Chunkserver返回给client要读的数据信息。

小结

本章节主要介绍了一致性模块以及读写流程,截止这里,GFS的大块的逻辑和设计理念都已经阐述的差不多了。还有一些细节的东西,例如文中提到的文件命名空间的原子性。以及GFS如何做到高可用(master主备设计)、数据完整性等设计这些单独开辟一节在后续的GFS文件系统剖析(下)-高可用、高可靠、数据完整性设计点剖析 中介绍,敬请期待。

参考资料

Google大数据处理的3篇核心论文 《The Google File System》:http://research.google.com/archive/gfs.html

《MapReduce: Simplified Data Processing on Large Clusters 》:http://research.google.com/archive/mapreduce.html

《Bigtable: A Distributed Storage System for Structured Data》:http://research.google.com/archive/bigtable.html

百度百科:http://baike.baidu.com/subview/805525/13773509.htm

本文来自网易实践者社区,经作者崔晓晴授权发布。