GFS是由Google设计并实现的一个可扩展的分布式文件系统,它适用于大型分布式计算密集型应用程序。与一般分布式文件系统具有诸多相同点,如性能、可伸缩性、可靠性和可用性,它的设计由Google应用工作负载和技术环境驱动。提出了一些不同的设计观点,包括: 1.文件系统由成百上千台廉价计算部件构成,组件发生故障是常态,因此持续监控、错误检测、容错和自动恢复必须是系统的组成部分; 2.传统标准的文件很大,而实际可能会处理大量(数亿)小文件(KB级),需要重新考虑设计I/O和Block Size参数; 3.文件通过Append发生改变,几乎不存在随机写,一旦写入文件只能读取(顺序读取),因此Append成为性能优化和原子性保证的重点,客户端缓存数据块则失去吸引力; 4.通过共同设计应用程序和文件系统的API有利于提高整个系统的灵活性
设计概述
1. Assumptions
系统构建于许多廉价的商用计算机上,发生故障是常态。它必须持续监控自己,在常规基础上及时检测、容错和恢复故障组件
适量存储大文件,必须支持小文件
有大型流式读和小型随机读两种类型
也有许多大型顺序写入,可以将数据附加到文件,文件一旦编写很少再次修改
系统文件通常用作生产者-消费者队列并用于多向合并,每台机器上可能运行上百个生产者同时append数据到文件,因此系统必须有效实现客户端并发append的语义
高持续带宽比低延迟更重要,大多数目标应用程序都以高速率批处理数据,很少有对单个读取或写入有严格响应时间要求
2. Interface
GFS提供了熟悉的文件系统接口,文件在目录中按层次结构组织,并由路径名称标识。支持: create,delete,open,close,read, write files等常规操作,此外,GFS还有snapshot和record append操作。Snapshot以低成本方式创建一个文件或目录树的副本; Record Append运行多个客户端同时将数据append到同一文件,同时保证每个客户端append操作的原子性。多个客户端可以同时append不需要添加锁,这对于实现多路合并结果和生产者-消费者队列很有用
-
Architecture
Figure 1: GFS Architecture
如图一,单个Master、多个chunkservers和多个访问clients组成一个GFS集群,其中每一个部分通常都是以用户级别的服务进程运行于商用Linux机器。只要机器资源允许并且可以接受运行可能不稳定的应用程序代码导致的低可靠性,那么在同一台机器上运行一个chunkserver和一个client也很容易
文件分为固定大小的chunk。每一个chunk在创建时Master会为它分配一个全局唯一且不可变的chunk handle(占64bit)标志符; ChunkServers基于Linux file system在本地磁盘以Linux files存储chunks; Clients通过指定chunk handle和byte range读/写chunk数据; 默认情况下每个chunk有三个副本,存储于不同的ChunkServer,用户也可以为File namespace中不同区域指定不同的副本级别
Master维护所有文件系统的元数据,包括命名空间、访问控制信息、文件到chunks的映射以及chunks的当前位置; 它还控制整个系统范围的活动,例如块租约管理、孤立块的垃圾回收及ChunkServer间的块迁移; Master与ChunkServers之间定期传递HeartBeat消息进行通信,Instructions to chunkserver并且收集ChunkServer state
链接到每个应用程序的GFS client代码实现文件系统API,读/写数据需要Master和ChunkServers通信,clients与Master之间通过元数据操作进行交互,所有的data-bearing通信都直接进入ChunkServers; GFS没有提供POSIX API,因此无需挂钩Linux vnode层
GFS clients和ChunkServers都不缓存文件数据(但是会缓存元数据),大多数应用程序流通过的文件或文件集太大而无法缓存,它们没有通过消除缓存一致性问题简化客户端和整个系统; ChunkServers将Chunks存储为本地文件,Linux的缓冲区缓存已经将频繁访问的数据保存在内存中,因此也不需要缓存文件数据
4. Single Master
Master可以地极大简化设计并且能够使用全局知识进行复杂的chunk和副本放置决策,尽量减少参与读写避免其成为系统瓶颈; Clients询问Master应该与哪些ChunkServers,在一定时限内缓存这些信息并直接与ChunkServers交互进行许多后续操作,但永远不会通过Master读取数据
数据读取流程
(a) 使用固定的块大小,客户端将应用程序指定的file name和字节偏移量转换为chunk index
(b) GFS client向Master发送包含file name和chunk index的请求; Master回复相应的chunk handle和chunk locations; GFS client使用file name和chunk index作为key缓存此信息。事实上,在同一个请求中客户端通常批量发送多个chunks的file name和chunk index,Master响应请求中所有的chunks信息,这些额外的信息可以减少client-master交互而不需要多余的开销
(c) GFS client指定chunk handle和byte range向其中一个副本所在的ChunkServer发送请求(最可能是最接近的副本)。除非缓存信息过期或者文件被重新打开,否则GFS clients读相同的chunks不再需要与Master交互
(d) GFS clinet收到ChunkServer返回的chunk data
5. Chunk Size
块大小是关键设计参数之一,GFS选择64MB(比典型文件系统chunk size大得多),每一个块副本作为一个普通的Linux文件存储于ChunkServer并且仅在需要时进行扩展,使用惰性空间分配避免由于内部碎片造成空间浪费
Large Chunk Size优势
(a) 减少客户端与Master交互,同一块上的读写操作只需要Master发送一个初始化请求获取块的位置信息
(b) 维持一个持久的TCP连接减少网络开销
(c) 减少存储在 Master上的元数据
6. Metadata
Master存储三种元数据
(a) 文件和块命名空间
(b) 文件和块映射
(c) 每个块副本位置
(a),(b) 两类元数据通过operation logs持久化存储到Master本地磁盘及复制到远程机器; 使用日志允许我们简单可靠地更新Master状态,不用担心Master Crash时出现不一致风险; Master不对Chunk位置信息做持久化存储,而是在每次Master启动或者chunkserver加入集群时,询问每一个ChunkServer关于它的chunks信息
7. Consistency Model
GFS具有宽松的一致性模型,分布式应用程序可以通过一些简单的技术来很好地适应一致性模型(relying on appends rather than overwrites,checkpointing, and writing self-validating, self-identifying records),实现起来也相对简单和高效
文件命名空间Mutation(e.g. 文件创建)是原子的。它们完全由Master处理: 命名空间锁保证原子性和正确性; Master的operation log定义了这些操作总的全局顺序。数据突变后文件区域的状态取决于Mutation类型是成功还是失败,以及是否存在并发突变(如下图总结)

系统交互
设计GFS原则: 所有操作中Master参与度最小化,在此背景下描述Client、Master及ChunkServers之间的交互实现流程

1.客户端询问Master当前由哪一个Chunkserver持有租约以及其他副本的位置。如果没有Chunkserver拥有租约,Master选择一个副本授予之
2.Master响应主要副本的标识和其他辅助副本的位置,客户端缓存此数据以用于将来的Mutation,只有当primary无法访问或不再持有lease时,client才需要再次联系Master
3.客户端以任意顺序Push数据到所有的Replicas,每一个Chunkserver将会存储数据在内部LRU缓冲区缓存中,直到数据被使用或过期。从控制流中解耦数据流,我们可以基于网络拓扑调度数据流提高性能
4.一旦所有副本都确认接收到数据,客户端就会向Master发送写请求。该请求标识先前推送到所有副本的数据,主要为其接收到
5.主副本转发所有写请求给辅助副本。每一个辅助副本以相同序列号顺序执行应用Mutation
6.辅助副本回复主副本表示它们已完成操作
7.主副本响应Client,副本遇到任何错误会报告给客户端。如果出现错误,写入可能在主要副本和辅助副本的任意子集上成功(如果Master发生故障,则不会为其分配序列号,也不会转发)。客户端请求被认为已失败,并且修改后的区域处于不一致状态。客户端通过失败重试来处理此类错误。它将在步骤(3)到(7)中进行一些尝试
小结
GFS设计决策适用于具有大规模和成本意识的数据处理任务,它的成功应用也有诸多值得学习借鉴之处
Next: Paxos

