Google分布式系统三⼤论⽂(⼀)TheGoogleFileSystem The Google File System 中⽂版
摘要
我们设计并实现了Google⽂件系统,⼀个⾯向分布式数据密集型应⽤的、可伸缩的分布式⽂件系统。虽然运⾏在廉价的⽇⽤硬件设备上,但是它依然了提供容错功能,为⼤量客户机提供了很⾼的总体性能。
虽然与很多之前的分布式⽂件系统有很多相同⽬标,但是,我们的设计已经受应⽤的负载情况和技术环境影响,现在以及可预见的将来都反映出,我们的设计和早期的分布式⽂件系统的设想有了显著的分离。这让我们重新审视了传统⽂件系统在设计上的选择,探索彻底不同的设计点。
GFS成功满⾜了我们的存储需求。其作为存储平台被⼴泛的部署在Google内部,该平台⽤来产⽣和处理数据,这些数据被我们的服务以及需要⼤规模数据集的研究和开发⼯作使⽤。迄今为⽌,最⼤的⼀个集利⽤⼀千多台机器上的数千个硬盘,提供数百TB的存储空间,同时被数百个客户机访问。
在本论⽂中,我们展⽰了设计⽤来⽀持分布式应⽤的⽂件系统接⼝的扩展,讨论我们设计的许多⽅⾯,最后对⼩规模基准测试和真实使⽤作了测量报告。
常⽤术语
设计,可靠性,性能,测量
关键词
容错,可伸缩性,数据存储,集存储
1. 简介
为了满⾜Google迅速增长的数据处理需求,我们设计并实现了Google⽂件系统(Google File System–GFS)。GFS与之前的分布式⽂件系统有着很多相同的⽬标,⽐如,性能、扩展性、可靠性以及可⽤性。但是,我们的设计还受对我们的应⽤的负载和技术环境的观察的影响,现在以及可预见的将来都反映出,我们的设计和早期的分布式⽂件系统的设想有了显著的分离。这让我们重新审视了传统⽂件系统在设计上的选择,在设计上探索了彻底不同的设计点。
⾸先,组件失效被认为是常态事件,⽽不是意外事件。⽂件系统由⼏百乃⾄数千台由廉价的⽇常部件组装成的存储机器组成,同时被相当数量的客户机访问。部件的数量和质量事实保证了任意给定时间,⼀些部件⽆法⼯作,⼀些部件⽆法从它们⽬前的失效状态中恢复。我们遇到过如下原因导致的问题,⽐如应⽤程序bug、操作系统的bug、⼈为失误,甚⾄还有硬盘、内存、连接器、⽹络以及电源失效。因此,持久的监控、错误侦测、容错以及⾃动恢复必须集成在系统中。
其次,以传统的标准衡量,我们的⽂件⾮常巨⼤。数GB的⽂件⾮常普遍。每个⽂件通常包含许多应⽤
程序对象,⽐如web⽂档。当我们定期由数亿个对象构成的快速增长的数TB的数据集时,即使⽂件系统⽀持,管理数⼗亿KB⼤⼩的⼩⽂件也是不实⽤的。因此,设计的假设条件和参数,⽐如I/O 操作和Block的尺⼨都不得不重新考虑。
第三,绝⼤部分⽂件的变更是采⽤在追加新数据,⽽不是重写原有数据的⽅式。⽂件内部的随机写在实际中⼏乎不存在。⼀旦写完之后,⽂件只能读,⽽且通常只能顺序读。各种数据符合这些特性,⽐如:⼀些可能组成了数据分析程序扫描的超⼤数据集;⼀些可能是正在运⾏的应⽤程序⽣成的连续数据流;⼀些可能是档案数据;⼀些可能是由⼀台机器⽣成、另外⼀台机器处理的中间数据,同时处理或者稍后适时处理。考虑到这种针对海量⽂件的访问模式,数据的追加是性能优化和原⼦性保证的焦点所在,客户端对数据块缓存毫⽆吸引⼒。
第四,通过增加灵活性,应⽤程序和⽂件系统API的协同设计对整个系统有益。⽐如,我们放松了对GFS⼀致性模型的要求,不⽤在应⽤程序中强加繁重负担,⼤⼤简化了⽂件系统。我们甚⾄引⼊了原⼦性的追加操作,这样多个客户端可以对⼀个⽂件同时进⾏追加操作,不需要他俩之间额外的同步机制。这些问题会在下⽂进⾏详细讨论。
当前针对不同⽬的部署了多重GFS集。最⼤的⼀个集拥有超过1000个存储节点,超过300TB的硬盘存储,被不同机器上的数百个客户端连续不断的频繁访问。
2.设计概述
2.1 假设
在设计满⾜我们需求的⽂件系统时候,我们被这种现实指引着:我们的假设既有机遇、⼜有挑战。我们之前提到⼀些关键关注点,现在更详细地展⽰我们的假设。
魔方教程一步一步图解入门第3讲系统由许多廉价的⽇⽤组件组成,组件失效是⼀种常态。系统必须持续监控⾃⾝状态,探测,处理容错并且迅速地恢复失效的组件。
系统存储⼀定数量的⼤⽂件。我们预期会有⼏百万个⽂件,⽂件的⼤⼩通常在100MB或者以上。数个GB⼤⼩的⽂件也是普遍存在,并且应该被被有效的管理。系统也必须⽀持⼩⽂件,但是不需要针对⼩⽂件做优化。
系统的⼯作负载主要由两种读操作组成:⼤规模的流式读取和⼩规模的随机读取。⼤规模的流式读取中,单次操作通常读取数百KB的数据,更常见的是⼀次读取1MB甚⾄更多的数据。来⾃同⼀个客户机的连续操作通常是读取⼀个⽂件中⼀个连续区域。⼩规模的随机读取通常是在任意位移上读取⼏个KB数据。对性能敏感的应⽤程序通常把⼩规模的随机读取分批处理并排序,如此在⽂件中稳固前进⽽不是往复来去。
系统的⼯作负载还包括许多⼤规模的、顺序的、对⽂件追加数据的写操作。通常操作的⼤⼩和读操作类似。数据⼀旦被写⼊后,⽂件很少被再次变更。系统⽀持在⽂件任意位置写⼊的⼩型操作,但是这种操作不必⾼效。
系统必须⾼效的、定义明确的(alex注:well-defined)实现多客户端并⾏追加数据到同⼀个⽂件⾥的语意。我们的⽂件通常作为“⽣产者-消费者”队列,或者为多路合并使⽤。数百个⽣产者,每台机器上运⾏⼀个,并发地对⼀个⽂件进⾏追加。最⼩同步开销原⼦性是必要的。⽂件可以在稍后读取,或者是消费者在追加同时读取⽂件。
⾼可持续的⽹络带宽⽐低延迟更重要。我们的⼤多数⽬标程序很看重⾼速率⼤批量地处理数据,极少有程序对单⼀的读写操作有严格的响应时间要求。
2.2 接⼝
GFS 提供了⼀套常见的⽂件系统接⼝,尽管它没有实现像POSIX的⼀套标准API。⽂件以⽬录的形式分层组织,⽤路径名标识。我们⽀持常⽤的操作,如创建、删除、打开、关闭、读写⽂件。
另外,GFS 有快照和记录追加操作。快照以很低的成本创建⼀个⽂件或者⽬录树的拷贝。记录追加操作允许多个客户端同时对⼀个⽂件进⾏追加,同时保证每个单⼀客户端追加的原⼦性。多个客户端可
以在不需要额外锁的情况下,同时追加数据,这对于实现多路结果合并,以及”⽣产者-消费者”队列⾮常有⽤。我们发现这些类型的⽂件对于构建⼤型分布应⽤是⾮常重要的。快照和记录追加操作将在3.4和3.3节分别作进⼀步讨论。
2.3 架构
⼀个GFS 集包含⼀个单独的master(alex注:这⾥的⼀个单独的master节点的含义是GFS系统中只存在⼀个逻辑上的master组件。后⾯我们还会提到master节点复制,因此,为了理解⽅便,我们把master节点视为⼀个逻辑上的概念,⼀个逻辑的master节点包括两台物理主机,即两台master服务器)和多台chunkserver,并且被多个客户端访问,如图1 所⽰。其中的每台机器通常都是运⾏着⽤户级别(user-level)的服务器进程的⽇常Linux机器。我们可以很容易地把chunk服务器和客户端都运⾏在同⼀台机器上,前提是机器资源允许,并且运⾏古怪应⽤程序代码⽽引起的不可靠性是可以接受的。
⽂件都被分割成固定⼤⼩的块。在创建块的时候,master会对每个块分配⼀个不可变的、全球唯⼀的64位的块句柄来进⾏标识。Chunkserver将块作为linux ⽂件保存在本地硬盘上,并且根据指定的块句柄和字节范围来读写块数据。为了可靠,每个块都会复制到多个chunkserver上。缺省情况下,我们存储三个副本,不过⽤户可以为不同的⽂件命名空间区域指定不同的复制级别。
Master维护所有的⽂件系统元数据。这些元数据包括命名空间、访问控制信息、⽂件到块的映射信息
、以及当前块的位置。Master节点还控制着系统范围内的活动,⽐如,块租⽤管理(alex注:BDB也有关于lease的描述,不知道是否相同) 、孤⽴块的垃圾回收、以及块在Chunkserver间的迁移。Master⽤⼼跳信息周期地和每个Chunkserver通讯,向Chunkserver发送指令并收集其状态。
链接到每个应⽤程序⾥的GFS客户端代码实现了⽂件系统API,代表应⽤程序与master和Chunkserver通讯以及读写数据。客户端和master交互元数据操作,但是所有承载数据的通信都是直接和Chunkserver进⾏的。我们不提供POSIX标准的API,因此不需要在Linux vnode层建⽴钩⼦程序。
⽆论是客户端还是Chunkserver都不需要缓存⽂件数据。客户端缓存收效甚微,因为⼤多数程序要么流式处理⼀个巨⼤⽂件,要么⼯作集太⼤根本⽆法缓存。通过消除缓存相关的问题简化了客户端和整个系统。(然⽽,客户端会缓存元数据。)Chunkserver不需要缓存⽂件数据的原因是,数据块以本地⽂件的⽅式保存,Linux 缓冲区已经把经常访问的数据缓存在了内存中。
2.4 单⼀Master节点
单⼀的master节点极⼤地简化了我们的设计。并且使master可以使⽤全局信息(knowledge)进⾏复杂的块部署和副本决策。然⽽,我们必须使其与读写的相关性降到最⼩,以避免其成为瓶颈。客户端绝不通过master读写⽂件数据。相反,客户端询问master它应该联系哪⼀个Chunkserver。客户端将这些信息缓存有限的时间,后续的操作直接和Chunkserver进⾏交互。
我们参考图1解释⼀次简单读取交互。⾸先,客户端使⽤固定的块⼤⼩将应⽤程序指定的⽂件名和字节偏移转换成⽂件中的块索引。然后,它将包含⽂件名和块索引的请求发给master。Master回复相应的块句柄和副本位置信息。客户端使⽤⽂件名和Chunk索引作为键缓存这些信息。
数据查询语言的主要sql语句是之后客户端发送请求到其中的⼀个副本处,⼀般是最近的。请求信息指定了Chunk句柄和块内字节范围。对同⼀个块的进⼀步读取不再需要客户端和master的交互了,知道缓存信息过期或者⽂件被重新打开。实际上,客户端通常会在⼀次请求中请求多个块,master也可能⽴刻包含了那些请求之后的块信息。这些额外的信息在实际上没有任何额外代价的情况下,回避了客户端和master未来⼀些可能的交互。
2.5 Chunk尺⼨
块的⼤⼩是关键的设计参数之⼀。我们选择了64MB,这个尺⼨远远⼤于通常⽂件系统的块⼤⼩。每个块副本都以普通Linux⽂件的形式保存在Chunkserver上,只有在需要的时候才扩⼤。惰性空间分配策略避免了由于内部碎⽚导致的空间浪费,也许这是对这么⼤的块尺⼨最有争议的地⽅。
⼤尺⼨块有⼏个重要的优势。⾸先,它减少了客户端和master交互的需求,因为对同⼀块的读写只需要⼀次和mater的初始请求来获取块的位置信息。这种缩减我们的⼯作负载⾄关重要,因为应⽤程序⼤都是连续读写⼤⽂件。即使是⼩规模的随机读取,客户端可以轻松地为⼀个数TB的⼯作集缓存所有的
块位置信息。其次,因为采⽤较⼤块,客户端很可能对⼀个块执⾏多次操作,这样可以通过与Chunkserver在很长的时间内保持持久的TCP 连接来减少⽹络负载。再者,它减少了在master上保存的元数据的⼤⼩。这就允许我们把元数据保存在内存中,这也带来了其它优势,我们将在在2.6.1节进⾏讨论。
另⼀⽅⾯,即使配合惰性空间分配,⼤尺⼨块也有其缺点。⼩⽂件包含少量的块,甚⾄只有⼀个块。如果许多客户端访问同⼀个⼩⽂件,存储这些块的Chunkserver就会变成热点。在实际中,由于我们的程序通常是连续的读取包含多个块的⼤⽂件,热点还不是主要问题。
然⽽,当GFS第⼀次被批处理队列系统使⽤的时候,热点问题还是显露了:⼀个可执⾏⽂件作为⼀个单块⽂件写在了GFS上,之后同时在数百台机器上启动。存放这个可执⾏⽂件的⼏个Chunkserver被数百个并发请求造成过载。我们通过使⽤更⼤的复制因⼦来保存这样的可执⾏⽂件,并且让批处理队列系统错开程序的启动时间的⽅法解决了这个问题。⼀个可能的长效解决⽅案是,在这种的情况下允许客户端从其它客户端读取数据(p2p?)。
2.6 元数据
Master(alex注:注意逻辑的master节点和物理的master服务器的区别。后续我们谈的是每个master服务器的⾏为,如存储、内存等等,因此我们将全部使⽤物理名称)存储3种主要类型的元数据:⽂件
和块的命名空间、⽂件到块k的映射、每个块副本的位置。所有的元数据都保存在master的内存中。前两种类型(命名空间、⽂件到块的映射)同时也会通过记录变更到操作⽇志的⽅式持久存储,该⽇志存储在master的本地磁盘上,并且在远程机器上备份。使⽤⽇志使我们能够简单可靠地更新master的状态,并且不⽤冒着万⼀master崩溃数据不⼀致的风险。Master不会持久存储块位置信息。相反,Master会在启动,或者有新的Chunkserver加⼊集时,向各个Chunkserver询问它们的块信息。
2.6.1 内存中的数据结构
因为元数据保存在内存中,master的操作⾮常快。并且,Master在后台周期性扫描⾃⼰的整个状态简单⾼效。这种周期性的扫描⽤于实现块垃圾收集、在Chunkserver失效时重新备份、通过块迁移来均衡跨Chunkserver的负载及磁盘使⽤状况。4.3和4.4章节将进⼀步讨论这些⾏为。
对于这种memory-only式的⽅法的⼀个潜在担⼼在于:块的数量亦即整个系统的容量受限于master所拥有的内存⼤⼩。这在实际应⽤中并不是⼀个严重的限制。Master为每个64M数据块维护少于64个字节的元数据就。由于⼤多数⽂件包含多个块,因此⼤多数块是满的,除了最后⼀个块是部分填充的。同样,每个⽂件需要的⽂件命名空间数据通常少于64字节,因为它使⽤前缀压缩紧密地存储⽂件名字。
如果有必要⽀持更⼤的⽂件系统,相⽐于通过在内存中存储元数据⽽获得的简洁性、可靠性、⾼性能
和灵活性⽽⾔,为master增加额外内存的费⽤也是很少的。
2.6.2 Chunk 位置信息
Master不保存哪个Chunkserver拥有指定块的副本的持久化记录。Master只是在启动的时候轮询Chunkserver以获取那些信息。Master 可以保持⾃⼰是最新的(thereafter),因为它控制了所有的块的部署,⽽且通过定期⼼跳信息监控Chunkserver的状态。
我们最初试图把块的位置信息持久地保存在master上,但是我们认定在启动的时候从Chunkserver请求数据,之后定期轮询更简单。这样消除了在Chunkserver加⼊、离开集、更名、失效、以及重启等情况发⽣的时候,保持master和Chunkserver同步的问题。在⼀个拥有数百台服务器的集中,这类事件会频繁的发⽣。
理解这个设计决策的另外⼀种⽅式就是认识到:对于块是否在它的硬盘上,Chunkserver才说了算。试图在master上维护⼀个这些信息的⼀致视图是毫⽆意义的,因为Chunkserver上的错误可能会导致块不由⾃主地消失(⽐如,硬盘可能损坏不能⽤了),或者操作⼈员可能会重命名⼀个Chunkserver。
2.6.3 操作⽇志
操作⽇志包含了关键的元数据变更历史记录。这对GFS ⾮常重要。不仅是因为它是元数据的唯⼀持久
化记录,它也作为定义同步操作顺序的逻辑时间基线(alex注:也就是通过逻辑⽇志的序号作为操作发⽣的逻辑时间,类似于事务系统中的LSN)。⽂件和块,连同它们的版本(参考4.5节),都由它们创建时的逻辑时间唯⼀的、永久的标识。
由于操作⽇志⾄关重要,我们必须对其可靠地存储,并且只有在元数据的变化被持久化后,这种变化对客户才是可见的。否则,即使块本⾝没有问题,我们(effectively)有效地丢失整个⽂件系统或者最近的客户操作。因此,我们会把⽇志复制到多台远程机器,并且只有把相应的⽇志记录写⼊到本地以及远程硬盘之后,才会响应客户端的操作请求。Master会在写⼊之前将⼀些⽇志记录分批处理,从⽽减少写⼊和备份对整体系统吞吐量的影响。
Master通过重演操作⽇志恢复它的⽂件系统状态。为了缩短启动时间,我们必须保持⽇志很⼩(alex注:即重演系统操作的⽇志量尽量的少)。每当⽇志增长到超过⼀定⼤⼩的时候master对系统状态做⼀次检查点,(shijin:Checkpoint是⼀种⾏为,⼀种对数据库状态作⼀次快照的⾏为,将所有的状态数据写⼊⼀个Checkpoint⽂件,并删除之前的⽇志⽂件)如此⼀来,通过从本地磁盘加载最新检查点,然后仅仅重演检查点之后有限数⽬⽇志记录的⽅式,master即可恢复系统。检查点是⼀个紧密的类B-树格式,该格式可以直接映射到内存,可以在⽆需额外解析的情况下⽤于命名空间查询。这进⼀步提⾼了恢复速度,增强了可⽤性。
由于创建⼀个检查点需要⼀定的时间,所以master的内部状态被组织为这样的形式,这种形式可以在不阻塞后续变更操作的同时创建新的检查点。Master在⼀个独⽴的线程切换到新的⽇志⽂件并创建新的检查点。新的检查点包含切换前所有的变更。为⼀个有数百万⽂件的集创建⼀个检查点⼤约需要1 分钟的间。创建完成后,检查点被写⼊本地和远程硬盘中。
Master只需要最新的检查点和后续的⽇志⽂件。旧的检查点和⽇志⽂件可以被⾃主删除,但是为了提防灾难性故障(alex注:catastrophes,数据备份相关⽂档中经常会遇到这个词,表⽰⼀种超出预期范围的灾难性事件),我们还是会随⼿保存⼀些。创建检查点期间的失败不会影响正确性,因为恢复代码检测并跳过没完成的检查点。
2.7 ⼀致性模型
GFS有⼀个宽松的⼀致性模型,这个模型很好地⽀撑我们的⾼度分布的应⽤,但是却依然相当简单且可以⾼效实现。现在我们讨论GFS的⼀致性保障及其对应⽤程序的意义。我们也强调了GFS 如何维护这些保障,但是实现的细节将在本论⽂的其它部分讨论。
2.7.1 GFS⼀致性保障机制
⽂件命名空间的变更(例如,⽂件创建)是原⼦性的。它们只能由master控制:命名空间锁保证了原⼦性和正确性(4.1节);master的操作⽇志定义了这些操作的⼀个全局完整的顺序(2.6.3节)。
数据变更后⼀个⽂件域(alex注:region这个词⽤中⽂⾮常难以表达,我认为应该是变更操作所涉及的⽂件中的某个范围)的状态取决于操作的类型、成功与否、以及是否有同步变更。表1 汇总了结果。如果所有客户端,⽆论从哪个副本读取,总是看到相同的数据,那么我们认为⽂件域是“⼀致的”;在⼀个⽂件数据变更以后,如果⽂件域是⼀致的,并且客户端能够看到变更写⼊的完整内容,那么这个域是“已定义的”。当⼀个数据变更操作成功执⾏,没有受到同步写操作的⼲扰,那么受影响的域就是已定义的(暗含了⼀致性):所有的客户端总是可以看到变更写⼊的内容。并发变更操作成功完成之后,域处于⼀致的、未定义的状态:所有的客户端看到同样的数据,但是它⽆法反映任何⼀次变更写⼊写⼊的数据。通常情况下,⽂件域包含了来⾃多个变更的、混杂的数据⽚段。失败的变更操作导致这个域不⼀致(因此也是未定义的):不同的客户可能在不同的时间会看到不同的数据。下⾯我们描述了我们的应⽤程序如何区分已定义和未定义的域。应⽤程序没必要进⼀步区分未定义域的不同类型。
数据变更可能是写⼊或者记录追加。写⼊操作把数据写在应⽤程序指定的⽂件偏移位置上。即使在并发变更⾯前,记录追加操作⾄少把数据(记录)原⼦性的追加⼀次,但是是在GFS 选择的偏移上(3.3节)(alex注:这句话有点费解,其含义是所有的追加写⼊都会成功,但是有可能被执⾏了多次,⽽且每次追加的⽂件偏移量由GFS⾃⼰计算)。(相⽐⽽⾔,通常的追加不过在这样⼀个偏移位置写,客户认为偏移位置是⽂件的当前的尾部。)GFS返回给客户端⼀个偏移量,该偏移量标明了包含了记录的、已定义的域的起点。另外,GFS 可能会在中间插⼊填充数据或者重复记录。它们占据被认定为不⼀致的域,并且这些数据和⽤户数据相⽐通常很⼩。
经过了⼀系列的成功的变更操作之后,GFS保证被变更的⽂件域是已定义的,并且包含最后⼀次变更操作写⼊的数据。GFS 通过以下措施达成⽬的:(a) 对块的所有副本应⽤相同顺序的变更(3.1节),(b)使⽤块版本号来探测过期的副本,过期的副本是由于它所在的Chunkserver宕机(4.5章)期间错过了变更⽽引起的。过期的副本不会涉及变更,也不会返回给向master请求块位置信息的客户端。它们优先被垃圾收集。
由于客户端缓存块位置信息,所以在信息刷新前,客户端有可能从⼀个失效的副本读取了数据。这个时间窗⼝受限于缓存条⽬的超时时间和⽂件⽂件下⼀次被打开的时间,⽂件的再次打开会从缓存中清除该⽂件的块信息。并且,鉴于我们的⼤多数⽂件都是只进⾏追加操作,⼀个失效的副本通常返回⼀个提前结束的块⽽不是过期的数据。当⼀个Reader(alex注:本⽂中将⽤到两个专有名词,Reader和Writer,分别表⽰执⾏GFS读取和写⼊操作的程序)重新尝试并联络master时,它就会⽴刻得到当前的块位置信息。
变更操作成功执⾏很长时间之后,组件的失效仍然可以损坏或者销毁数据。GFS通过master和所有Chunkserver定期“握⼿”的⽅式来识别失效的Chunkserver,并且通过检查检验和来校验数据是否损坏(5.2节)。⼀旦问题浮出⽔⾯,数据要从效副本快速恢复(4.3节)。只有块的所有副本在GFS作出反应前全部丢失,该块才会不可逆转的丢失。GFS的反应时间(alex注:指master节点检测到错误并采取应对措施)通常是⼏分钟。即使在这种情况下,块变得不可⽤了,⽽不是损坏了:应⽤程序会收
到清晰的错误信息⽽不是损坏的数据。
2.7.2 程序的实现
GFS 应⽤程序可以利⽤⼀些简单技术适应这个宽松的⼀致性模型,这些技术已经满⾜了其他⽬的的需要:依赖追加⽽不是重写,检查点,⾃验证,⾃标识的记录。
实际中,我们所有的应⽤通过追加⽽不是重写的⽅式变更⽂件。⼀种典型的应⽤中,写⼊程序从头到尾地⽣成⼀个⽂件。写完所有数据之后,程序原⼦性地将⽂件重命名为⼀个永久的⽂件名,或者定期地对成功写⼊了多少数据设置检查点。检查点也可以包含程序级别的检验和。Readers仅校验并处理上⼀个检查点之后的⽂件域,也就是⼈们知道的已定义状态。不管⼀致性和并发问题的话,该⽅法对我们很适合。追加⽐随机写更有效率,对程序失败有更弹性。检查点允许Writer递增地重启,并且防⽌Reader成功处理从应⽤程序的⾓度看来并未完成的写⼊的⽂件数据。
在另⼀种典型应⽤中。许多Writer为了合并结果或者作为⽣产者-消费者队列并发地向⼀个⽂件追加数据。记录追加的“⾄少追加⼀次”的语义维持了每个Writer的输出。Reader使⽤下⾯的⽅法来处理偶然的填充和重复。Writer准备的每条记录中都包含了类似检验和的额外信息,以便⽤来验证它的有效性。Reader可以⽤检验和识别和丢弃额外的填充数据和记录⽚段。如果偶尔的重复内容是不能容忍的(⽐如,如果这些重复数据将要触发⾮幂等操作),可以⽤记录的唯⼀标识来过滤它们,这些标识符也通常
⽤于命名相应程序实体,例如web⽂档。这些记录I/O功能(除了剔除重复数据)都包含在我们程序共享的代码库(library code)中,并且适⽤于Google内部其它的⽂件接⼝实现。这样,记录的相同序列,加上些许重复数据,总是被分发到记录Reader中。
3. 系统交互
我们设计这个系统⼒图最⼩化master与所有操作的牵连。在这样的背景下,我们现在描述客户机、master和Chunkserver如何交互以实现数据变更、原⼦记录追加以及快照功能。
linux结课论文人工智能编程基础3.1 租约(lease)和变更顺序
变更是改变块内容或者块元数据的操作,⽐如写操作或者追加操作。每次变更在块所有的副本上执⾏。我们使⽤租约(lease)来维护副本间的⼀致性变更顺序。Master向其中⼀个副本授权⼀个块租约,我们把这个副本叫做主副本。主副本为对块的所有变更选择⼀个序列。应⽤变更的时候所有副本都遵照这个顺序。这样,全局变更顺序⾸先由master选择的租约授权顺序规定,然后在租约内部由主副本分配的序列号规定。
设计租约机制的⽬的是为了最⼩化master的管理开销。租约的初始过期时间为60秒。然⽽,只要块正在变更,主副本就可以请求并且通常会得到master⽆限期的延长。这些延长请求和批准信息附在mast
er和所有Chunkserver之间的定期交换的⼼跳消息中。Master有时可能试图在到期前取消租约(例如,当master想令⼀个在⼀个重命名的⽂件上进⾏的修改失效)。即使master和主副本失去联系,它仍然可以安全地在旧的租约到期后和向另外⼀个副本授权新的租约。
在图2 中,我们根据写操作的控制流程通过这些标号步骤图⽰说明了这⼀过程。
1.客户机询问master哪⼀个Chunkserver持有该块当前的租约,以及其它副本的位置。如果没有chunkserver持有租约,master将租约授权给它选择的副本(没有展⽰)。
2.master将主副本的标识符以及其它副本(次级副本)的位置返回给客户机。客户机为将来的变更缓存这些数据。只有在主副本不可达,或者其回应它已不再持有租约的时候,客户机才需要再⼀次联系master。
3.客户机将数据推送到所有副本。客户机可以以任意的顺序推送数据。Chunkserver将数据存储在内部LRU 缓存中,直到数据被使⽤或者过期。通过将数据流和控制流解耦,我们可以基于⽹络拓扑⽽不管哪个Chunksever上有主副本,通过调度昂贵的数据流来提⾼系统性能。3.2章节会作进⼀步讨论。
4.当所有的副本都确认接收到了数据,客户机对主副本发送写请求。这个请求标识了早前推送到所有副本的数据。主副本为接收到的所有变更分配连续的序列号,由于变更可能来⾃多个客户机,这就提
供了必要的序列化。它以序列号的顺序把变更应⽤到它⾃⼰的本地状态中(alex注:也就是在本地执⾏这些操作,这句话按字⾯翻译有点费解,也许应该翻译为“它顺序执⾏这些操作,并更新⾃⼰的状态”)。
5.主副本将写请求转发(forward)到所有的次级副本。每个次级副本依照主副本分配的序列顺序应⽤变更
6.所有次级副本回复主副本并标明它们已经完成了操作。
7.主副本回复客户机。任何副本遇到的任何错误都报告给客户机。出错的情况下,写操作可能在主副本和次级副本的任意⼦集上执⾏成功。(如果在主副本失败,就不会分配序列号和转发。)客户端请求被认定为失败,被修改的域处于不⼀致的状态。我们的客户机代码通过重试失败的变更来处理这样的错误。在退到从头开始重试之前,客户机会将从步骤(3)到步骤(7)做⼏次尝试。
clashforandroid教程6岁编程入门先学什么如果应⽤程序⼀次的写⼊量很⼤,或者跨越了多个块的范围,GFS客户端代码把它分成多个写操作。它们都遵照上⾯描述的控制流程,但是可能会被来⾃其它客户机的并发操作造成交错或者重写。因此,共享⽂件域可能以包含来⾃不同客户机的⽚段结尾,尽管如此,由于这些单个的操作在所有的副本上都以相同的顺序完成,副本仍然会是完全相同的。这使⽂件域处于2.7节提出的⼀致但是未定义的状态。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论