The Google File System
6.5840/6.824 Lab与笔记汇总
本文为笔者阅读论文The Google File System过程中的随笔记录,较为随意,希望能为大家提供一点理解上的帮助
1 Introduction
Google文件系统的设计是由Google对应用程序工作负载以及技术环境的调研所驱动的,它重新研究了传统设计,并提出一些完全不同的设计点。
首先,构成文件系统的固件非常容易故障,应该把组件故障作为常态进行考虑。因此,持续监控、错误检测、容错和自动恢复功能是文件系统必须考虑的因素。
其次,按照传统标准,文件是较大的,数GB的文件很常见。必须重新设计一些默认参数,如文件块大小。
第三,大多数文件是通过追加新数据来修改的,文件的随机写入实际上不存在。内容一旦写入,就只会被读取,且通常只能按顺序读取。鉴于这种大型文件的访问模式,追加新数据的方式成为性能优化和原子性保证的关键。
第四,应用程序与文件系统API的共同设计使Google的整个体统受益匪浅,因为可以极大程度上增加设计灵活性。例如,可以放款GFS的一致性模型,极大地简化文件系统;引入原子追加操作,使多个客户端可以同时追加到一个文件,而无需进行额外的同步操作。
2 Design Overview
2.1 Assumptions
此论文所提出的文件系统设计建立在以下的假设之上:
- 系统由许多经常发生故障的组件构成
- 系统会存放一定数量的大文件,通常为100MB或者更大,几GB的文件也是常见情况;系统也要支持小文件,但是不需要针对它们进行优化
- 工作负载主要由两种读取组成:大型流读取以及小型随机读取
- 工作负载也有许多大型顺序写入;也需要支持随机小型写入,但不需要优化它们
- 系统必须高效地为并发追加到同一文件的多个客户端实现定义明确的语义学,具有最小同步开销的原子性是必不可少的
- 持续的高带宽比低延迟更重要
2.2 Interface
GFS提供了熟悉的文件系统接口,尽管它没有实现像POSIX这样的标准应用编程接口。文件在目录中分层存放,并由路径名标识。GFS支持创建、删除、打开、关闭、读取和写入文件的常用操作。
此外,GFS具有快照与记录追加操作。快照操作以低成本创建文件或目录树的副本;记录追加操作则允许多个客户端同时见数据追加到同一个文件中,并保证每个客户端追加操作的原子性。(具体细节在3.3和3.4节讨论)
2.3 Architecture
GFS集群由单个主服务器(master)和多个块服务器(chunk servers)组成,并由多个客户端访问,如图1所示。
chunk server
文件被分成固定大小的chunks,每个chunk由master在创建时分配的不可变且全局唯一的64位chunk handle所标识。chunk server将chunk作为普通的linux文件存储在其本地磁盘上,根据客户端的请求,读取或写入特定的chunk。为了chunk的可靠性,每一个chunk会在多个chunk服务器上存储多份copy。(默认存储三份)
master
master维护文件系统所需的所有元数据,包括命名空间(namespace)、权限控制信息、文件到chunk的映射、chunk的位置。它还控制系统范围的活动,比如chunk租赁管理、chunk的垃圾回收机制以及chunk服务器之间的chunk迁移。master定期与HeartBeat消息中的每个chunk服务器进行通讯,发出指令并收集其状态。
client
GFS客户端代码实现了文件系统API,并与master以及chunk服务器通信,请求读取或写入数据。客户端与master交互以进行元数据操作,但所有数据通信都将直接与chunk服务器交互。
cache
客户端与chunk服务器都不缓存文件数据。客户端因为大多数应用程序流式传输大量文件或工作集太大而无法缓存,然而,客户端缓存元数据。
chunk服务器不需要缓存文件数据,因为文件存储在本地,可以利用linux自带的缓存。
2.4 Single Master
架构中只包含一个master节点,极大简化了设计,并使得其可以进行复杂的chunk分配,以及可以从全局出发进行chunk的复制决策。在设计中,需要减少master节点对读取和写入中的参与,这样它就不会成为瓶颈。客户端从不通过master读取或写入文件数据,而是询问master它应该从哪些chunk服务器请求。
2.5 Chunk Size
chunk大小是系统中关键的参数之一,GFS采用64MB的chunk大小,比典型的文件系统要大得多,每个chunk副本都作为普通的linux文件存储在chunk服务器上,并且根据需要进行扩展。
较大的chunk大小会为系统提供许多优势:
首先,它可以减少客户端与master交互的次数。其次,由于在一个较大的chunk上,客户端更有可能对给定chunk执行更多的操作,可以通过同一个TCP连接来请求master,减少网络开销。第三,它减少了存储在master上的元数据大小,使得元数据可以直接存储在内存中(具体的优势在2.6.1节讨论)。
当然,也会存在劣势:
小文件由少量的chunk组成,可能只由一个chunk组成,当许多客户端访问相同的小文件时,存储这个文件对应chunk的chunk服务器会面临很大压力。可以采用更多副本来解决这个问题,当然也可以让客户端从其他客户端读取数据,以缓解chunk服务器压力。
2.6 Metadata
master上存储的元数据主要分为三种类型:文件和chunk的命名空间、文件到chunk的映射以及每一个chunk的位置,所有的元数据均维护在master的内存中。
前两种类型也会通过操作日志的形式在master的硬盘上持久化,使用日志可以简单、可靠地更新master的状态,在master崩溃时不会导致数据不一致。
In-Memory Data Structures
由于元数据储存在内存中,因此master可以在后台非常便捷地扫描整块元数据,以进行chunk垃圾回收、chunk服务器故障时的重新复制、以及负载均衡时的chunk迁移。
Chunk Locations
master不会持久化记录chunk的位置,而是在启动时,询问所有的chunk服务器以获取chunk的位置。在这之后,由于chunk是由master进行管理的,所以master可以知道最新的chunk位置,通过HeartBeat消息监控chunk服务器状态。
chunk服务器对它自己的磁盘上的chunk是否可用是最清楚的,chunk服务器上的错误可能会导致chunk消失(例如,磁盘可能会损坏并被禁用),或者操作员可能会重命名chunk服务器。
Operation Log
操作日志包含关键元数据更改的历史记录,是GFS的核心。它不仅是元数据持久化的唯一记录,而且还作为定义并发操作顺序的逻辑时间线。文件、chunk以及它们的版本,都由它们创建时所打上的逻辑时间戳唯一标识。
由于操作日志至关重要,必须可靠地进行存储,并在元数据的更改进行持久化之前,使更改不对客户端可见。为了实现可靠性,GFS在多台远程机器上复制master的元数据,并在相应的日志记录持久化到master本地和远程磁盘后,才响应客户端请求。master会在持久化操作日志前,将多个日志记录批处理在一起,减少持久化和复制对整体系统吞吐量的影响。
master通过重新执行操作日志,来恢复文件系统的状态。为了缩短启动时间,需要保持日志量很小。当日志量超过一定大小时,master都会对其元数据创建checkpoint,以便以后可以从磁盘中加载最新的checkpoint,并在checkpoint基础上,重新执行较少数量的操作日志。checkpoint采用类似B树的紧凑形式进行存储,可直接映射到内存中,并可用于命名空间查找,无需额外解析,这进一步加快了系统启动速度。
构建checkpoint需要一定时间,故master切换到一个新的日志文件用于记录日志,并在单独的线程中创建新checkpoint。新checkpoint包含切换前的所有更改,在创建完成后,会被持久化进本地以及远程磁盘。
旧的checkpoint和日志文件可以删除。checkpoint创建期间的失败不影响系统正确性,因为恢复(启动)代码会检测并跳过不完整的checkpoint。
2.7 Consistency Model
GFS有一个宽松的一致性模型,将在本节进行讨论。
2.7.1 Guarantees by GFS
文件命名空间的修改具有原子性。master采用命名空间锁来确保原子性与确定性。
发生数据修改后,文件区域的状态取决于修改的类型、修改是否成功、以及是否存在并发修改。如下表所示。
如果所有客户端都能看到相同的数据,则文件区域是一致的(consistent),不管它们是从哪个chunk副本中读取。如果一个文件区域在文件数据修改后是一致的,并且客户端将看到所有修改的内容,则该区域是defined的。
由表中可以看出:
如果一个修改在没有并发干扰时成功,受影响的区域是defined的(当然也是一致的);并发成功的修改使得区域是undefined但一致的,所有客户端可以看到相同的数据,但是它可能看不到任何一个修改写入的内容;失败的修改会使区域变得不一致,不同的客户端可能会在不同时间看到不同数据。后面将讨论应用程序如何区分defined和undefined的区域。
数据修改的类型可能是write或者record appends。write会将数据写入到指定的文件偏移中。record appends则会将数据至少原子性地追加一次,即使存在并发修改。追加后最新的文件偏移会被返回到客户端,标识包含record的defined area的起始位置。此外,GFS系统可能会在文件中间插入padding或者record duplicates,它们会占据被认为不一致的区域。
由于客户端缓存chunk的位置,可能会在修改信息刷新前就从一个过时的chunk副本中读取内容。因此,当缓存的chunk信息超时,会清除特定的chunk信息;当文件被打开,则会清楚该文件的所有chunk信息。
GFS通过master与所有chunk服务器之间的定期握手,来识别故障的chunk服务器,并通过校验和来检测数据损坏情况。当某个chunk服务器出现问题时,会从chunk副本中恢复。只有chunk的所有副本在GFS处理之前全部损坏,这个chunk才会不可逆转地丢失。
2.7.2 Implications for Applications
GFS应用程序可以通过一些简单的技术来适应宽松的一致性模型,比如说,采用append技术而不是overwrites、创建checkpoint、编写自验证自识别的record。
3 System Interactions
本章主要讲述master、chunk服务器以及客户端之间的交互过程,以实现数据修改、原子记录追加以及快照创建。
3.1 Leases and Mutation Order
mutation是一种改变chunk内容或者修改元数据的操作,比如record append。每个mutation都会在chunk的所有副本上执行。GFS使用租赁技术(leases)来维护所有chunk副本之间mutation order(修改操作执行顺序)的一致性。
master会为其中一个chunk副本授予chunk lease,指定改chunk副本为primary。primary会为chunk的所有mutation选择一个序列顺序(serial order)。当其他副本要执行mutation时,均要遵循这个序列顺序来执行。因此,全局的mutation order由两部分组成,首先是master分配的lease grant order,然后是primary分配的serial number。
lease机制是为了减小master的管理负担。一个lease最初的超时时间为60s,但只要chunk一直被修改,primary就可以无限期地请求并接收master的延期扩展,通过HeartBeat消息。master有时会尝试在lease到期前进行撤销,比如当master想要中止重命名文件的mutation操作时。即使master丢失了与primary的通信,也可以在旧的lease到期时,将lease授予给一个新的chunk副本。
图2展示了一个客户端发出写入请求的例子,表现了各组件间的控制流与数据流。
- 1 客户端询问master,获取chunk服务器位置,并了解primary是哪个chunk服务器
- 2 master回复primary以及其他副本的位置
- 3 客户端将数据推送到所有副本,chunk服务器将数据存储在内部LRU缓冲区中,知道数据被使用或者过期
- 4 当所有chunk副本均确认收到数据时,客户端向primary发送写入请求。primary在收到写入请求时,为其收到的所有mutation分配连续的serial number,并按照顺序将mutation在本地执行
- 5 primary将写入请求转发到其他的chunk副本。其他的chunk副本依据primary给出的mutation order来执行所有mutation
- 6 所有副本回复primary,表示所有mutation均执行完成
- 7 primary回复客户端,表示请求成功或失败;如果失败,mutation的区域则会不一致,客户端将重试失败的mutation来处理此类错误。
当存在多个客户端的并发请求时,也是依据上述流程进行,但可能会使文件区域处于一致但未定义的状态。
3.2 Data Flow
为了充分利用每台机器的网络带宽,数据沿着chunk服务器链线性推送。因此,每台机器的全部出站带宽用于尽可能快地传输数据,而不是在多个接收者之间分配。
为了尽可能避免网络瓶颈和高延迟链路,每台机器都将数据转发到网络拓扑中尚未接收到数据的“最近”机器。假设客户端正在将数据推送到chunk服务器S1到S4。它将数据发送到最近的chunk服务器,例如S1。S1将其转发到最接近S1的chunk服务器S2到S4,例如S2。类似地,S2将其转发到S3或S4,依此类推。
最后,我们通过流水线传输通过TCP连接传输的数据来最小化延迟。在没有网络拥塞的情况下,将B字节传输到R个chunk副本的理想经过时间是B/T+RL,其中T是网络吞吐量,L是两台机器之间传输字节的延迟。
3.3 Atomic Record Appends
GFS提供了一种称为record append的原子追加操作。客户端只需指定数据,GFS选择文件偏移,将数据追加上去,然后将数据所在的偏移返回给客户端。
客户端将数据推送到文件最后一个chunk的所有副本中,然后发送append请求给primary。primary检查当前chunk的剩余空间是否足够用于append,如果不够,则会填充当前chunk的剩余空间,并告知客户端在下一个块上重新操作。
3.4 Snapshot
快照操作会在非常短时间内生成文件或者目录树的副本,并最小化对正在执行的mutation的影响。GFS使用copy-on-write技术来实现快照操作。当master收到快照请求时,它首先取消它将要创建快照的文件中chunk的任何未完成lease。然后,它将操作记录到磁盘中。之后,它通过复制源文件或目录树的元数据将此操作日志应用到其内存状态。快照文件与源文件指向相同的chunk。
在master创建完快照后,客户端需要重新询问master,获取当前的primary节点。假设当前客户端访问chunk C,且chunk C刚刚创建过快照,此时master会注意到chunk C的引用次数大于1。这时,master将选择一个新的chunk C‘,然后要求每个具有当前C副本的chunk服务器创建一个名为C’的新chunk。通过创建新chunk,确保了数据可以在本地复制,不需要通过网络。在此之后,主节点为chunk C’的一个副本授予lease,并回复客户端。
4. Master Operation
master执行所有命名空间的相关操作,此外,也管理整个系统的chunk(创建、协调、负载均衡等等)
4.1 Namespace Management and Locking
GFS在逻辑上将命名空间表示为完整路径名到元数据的映射。通过前缀压缩,该映射表可以在内存中高效地存储。命名空间树中的每个节点,都有一个相关联的读写锁。
每个master的操作需要在运行前获取一组锁。例如,假设当前操作涉及/d1/d2/…/dn/leaf,那么该操作会获取/d1、/d1/d2、/d1/d2/…/dn的读锁(类似数据库中B+树上锁的获取方式),然后根据具体操作,获取/d1/d2/…/dn/leaf的读锁或者写锁。
锁总以一定的总顺序获取以防止死锁:它们首先在命名空间树中按层级排序,并在同一层级中按字典序排列。
4.2 Replica Placement
chunk副本的placement策略(决定某个副本分发到哪个机器)有两个目的:最大限度地提高数据的可靠性和可用性,以及最大限度地提高网络带宽利用率。跨机器分发副本只能防止磁盘或机器故障并充分利用每台机器的网络带宽,还必须跨机架分发chunk副本,确保即使整个机架损坏,chunk的某些副本也能存在并保持可用。同时,跨机架分发可以利用多个机架的聚合带宽进行chunk的传输,这加快的读取的效率。但是,写传输也必须流经多个机架,这是做出的tradeoff。
4.3 Creation, Re-replication, Rebalancing
chunk副本的创建有三个原因:chunk创建、重新复制以及重新平衡
当master创建一个chunk时,它会选择放置副本的位置。它会考虑以下几个因素:
- 更希望将新副本放置在磁盘空间利用率低于平均水平的chunk服务器上
- 限制每个chunk服务器上最近创建的副本数量
- 希望将chunk副本分布在机架上
一旦可用副本的数量低于用户指定的目标,master就会重新复制一个chunk。每个需要重新复制的chunk的优先级基于几个因素。例如,我们对丢失了两个副本的chunk给予比只丢失了一个副本的chunk更高的优先级。此外,我们更喜欢首先为活动文件重新复制chunk,而不是属于最近删除文件的chunk(参见第4.4节)。最后,为了最大限度地减少故障对正在运行的应用程序的影响,我们提高了任何阻塞客户端进度的chunk的优先级。
master会定期重新平衡副本:它检查当前副本分布并移动副本以获得更好的磁盘空间和负载平衡。master必须选择要删除哪些现有副本,通常,它更喜欢删除空闲空间低于平均水平的块服务器上的副本,以均衡磁盘空间使用。
4.4 Garbage Collection
4.4.1 Mechanism
当文件被应用程序删除时,master会立即记录删除的操作日志,文件会被重命名为包含删除时间戳的隐藏名称。在master对文件系统命名空间的定期扫描期间,如果隐藏文件存在超过三天(这个时间可以配置),则master会删除它们。在此之前,文件仍然可以在新的特殊名称下读取,并且可以通过将其重命名为正常名称来取消删除。当隐藏文件从命名空间中删除时,其内存中的元数据将被删除,这有效地切断了它与所有块的链接。
在类似的chunk命名空间的常规扫描中,master识别孤立chunk(即那些无法从任何文件中访问的块)并擦除这些chunk的元数据。在与master定期交换的HeartBeat消息中,每个chunk服务器报告其拥有的块的子集,master回复不再存在于master元数据中的所有chunk。chunk服务器可以自由删除此类chunk的副本。
4.5 Stale Replica Detection
如果chunk服务器在故障期间,其中的某些chunk被修改了,那它携带的chunk副本版本会过时。对于每个chunk,master都会维护chunk version number(以下简称CVN)来区分最新版本与过时版本。
当master授予一个新的lease给一个chunk时,会增加CVN并通知最新的副本。master与这些副本都会将新的CVN在本地持久化。如果某个副本服务器故障了,那么它的CVN不会被增加。当故障的chunk服务器重新启动时,master会检测到该服务器中存在一个过时chunk副本。如果master看到一个比它记录中大的CVN,那么它会觉得自己在授予lease时故障了,那么会将更大CVN对应的chunk版本作为最新版本。
5. Fault Tolerance And Diagnosis
5.1 High Availability
在GFS集群中的数百台服务器中,有些服务器在任何给定时间都注定不可用。GFS通过两种简单而有效的策略保持整个系统的高可用性:快速恢复和复制
5.1.1 Fast Recovery
master和chunk服务器都被设计为,无论它们如何终止,都会恢复它们的状态,并能在几秒钟内启动。
5.1.3 Master Replication
为了可靠性,master的状态被复制,它的操作日志和checkpoints被复制到多台机器上。只有当它的日志记录被发送到本地磁盘和所有master副本上时,才会考虑提交状态的修改。一个master的进程负责所有修改以及后台活动,例如在内部更改系统的垃圾回收机制。当它发生故障时,它几乎可以立即重新启动。如果它的机器或磁盘发生故障,在GFS之外的监控基础设施会使用复制的操作日志在其他地方启动一个新的master进程。
5.2 Data Integrity
每个chunk服务器都使用校验和来检测存储数据的损坏。每个chunk服务器都必须维护自己的校验和来验证自己副本的完整性,因为1)通过比较chunk服务器之间的副本来检测副本完整是不切实际的;2)不同chunk服务器的同一chunk内容也可能不同。
每个chunk被分为若干个64KB的block,每个block都有一个相应的32位校验和。校验和像其他元数据一样保存在内存中,并通过操作日志持久化存储,与用户数据分开。
当客户端读取数据时,chunk服务器在返回数据前进行数据校验,如果数据出错。则chunk服务器向客户端返回错误并将错误报告给master。客户端会从其他chunk副本所在服务器读取,而master则会从另一个副本克隆chunk。在有效的新副本到位后,master指示报告错误的chunk服务器将损坏的副本删除。