一. 简介
2003 年 谷歌在第19届ACM操作系统原理研讨会(Symposium on Operating Systems Principles,SOSP)上,发表了论文《Google文件系统》,系统地介绍了Google面向大规模数据密集型应用的、可伸缩的分布式文件系统——GFS。
GFS是一个可扩展的大型数据密集型应用的分布式文件系统,该文件系统可在廉价的硬件上运行,并具有可靠的容错能力,该文件系统可为用户提供极高的计算性能,而同时具备最小的硬件投资和运营成本。
在如今看来 GFS 依旧是一个可用的分布式存储系统,Taobao File System
和Facebook Haystack
这两个存储系统就与 GFS 的结构类似,不过他们都优化了对小文件的支持,这部分 GFS 是没有的.
二. GFS 设计概要
1. 设计预期
任何系统都只能解决特定场景的特定问题.所以在讨论一个系统时应该明确该系统是为了解决什么问题,为这个问题付出了什么代价,做出了那些取舍.对 GFS 来说,有以下假设或限制
- 系统由许多廉价的设备组成, 所以系统要冗余和修复错误节点/组件.
- 系统存储大量(百万)大文件(100MB 以上),可以存储小文件但是没有优化.
- 系统支持两种类型读操作:大量的顺序读取以及小规模的随机读取.
- 系统高效、语义明确地支持多路并行追加数据到同一文件.
- 系统的写操作主要为大规模的、顺序的、数据追加方式的.
- 系统更看重平均响应时间,而不是单次的.
2. GFS 架构
一个 GFS 集群包含一个单独的 Master 节点(有备份),多台 Chunk Server, 同时被多台 Client 访问.
GFS 存储的文件都被分割成多个固定大小(64MB, 文件的最后一个 Chunk 允许不足 64MB)的 Chunk.每个 Chunk都一个唯一的 64 位 Chunk id.Chunk 服务器把 Chunk 以 Linux 文件的形式保存在本地磁盘上,并根据指定的 Chunk id 和 Range 来读写块数据.为防止数据丢失,默认使用 3 副本冗余, 不过也可以自己创建 namespace 指定不同的冗余策略.
Master 节点是元数据服务器.包括 namespace, 文件和 Chunk 的映射信息, Chunk 位置信息.前两者持久化在系统磁盘.Chunk 位置信息在每次启动时从 Chunk Server获取并一直实时更新.
Master 节点还管理系统内的调度,比如 Chunk Lease 管理,垃圾回收, Chunk 迁移等.Chunk Server 向 Master 发送心跳.
Client 代码以库的形式链接到用户代码中.Client 只从 Master 获取元数据,以此直接和 Chunk Server 通信.
Client 和 Chunk Server 都无需缓存文件数据.我们大部分时候都是流式读取一个巨大文件,或者工作集太大无法缓存,很少小范围内随机多次访问.并且放弃缓存也会简化系统的设计与实现.并且 Linux 系统会将经常访问的数据缓存在内存中.
3. 单一 Master 节点
单一 Master 节点 结构简单. 单一 Master 节点进行Chunk 管理时很精准.Master 节点避免直接读写文件,应该只是作为元数据服务器.下面描述一次读操作:
- Client 根据文件名和偏移量和 Chunk 固定的 Size 计算出 Chunk 索引
- Client 将文件名和 Chunk 索引发送到 Master(可以一次请求多个Chunk)
- Master 将对应的 Chunk id 和 副本位置信息发还给客户端
- Client 就近选择一个副本处进行读操作.
4. Chunk 尺寸
Chunk 大小选择了固定的 64MB, 这个大小在2003看来很大,但在如今就感觉蛮合适.当时主要考虑的是:
- 减少了Master 节点和 Client 的通讯需求.一次请求可以覆盖大范围的文件
- Client 也可以轻松的缓存数 TB 的工作数据的 Chunk 元数据
- 减少了 Master 元数据需要保存的数量
- Client 可以和 Chunk Server 一次 TCP 连接可以保持较长时间
6. 一致性模型
GFS 使用单一 Master 节点和 Lease 机制来保证分布式操作下语义正确,从而保证副本内各节点的一致性.同时 Chunk Server定时向 Master 发送心跳,Master 可以及时的进行垃圾回收和 Chunk 迁移,保证副本内各节点的一致性.
三. 详细设计
1. Master 服务器的复制
上文说到 GFS 集群中只有一个 Master 节点,这个一个是逻辑上的一个.实际上有多台机器(只保存操作日志和快照,不启动 Master 实例)来保障 GFS 集群的可靠性.Master 服务器的操作日志和 checkpoint文件会被复制到其他备用机器上,并且所有操作都是同步的,即 Master 和 所有备份机器操作日志和 checkpoint都写成功这次修改才能够提交成功.
Master 的故障转移由外部的监控程序进行,当提供 Master 服务的节点发生错误且不能恢复,由外部监控程序在Master 备份节点上启动新的 Master 进程并快速恢复服务(Chunk 位置信息会主动向所有 Chunk Server 获取一次,其他信息操作日志和 checkpoint 中有).同时客户端规范使用类似DNS 的操作将 Master 的地址改为最新的.
GFS 中还有一些 Master 服务器的副本(影子),当 Master 服务器宕机时提供只读操作.不过影子 Master 服务器更新是异步的,也就是 Master 和影子 Master 可能会有短暂的不一致发生.
Master 在宕机时集群会短暂停止写服务,不过仍然可以提供读服务.Master 会在短暂时间间隔内恢复
2. lease 和 写 Chunk 流程
为了保证 Chunk 与其副本在并发修改下保持一致,GFS 使用了 Lease 机制, 简单描述就是一个全局资源锁, Client 需要 Master 保证在一段时间内当前 Chunk 不会有未知的变化(例如被其他人修改),具体可见Lease 机制
注意:应该在熟悉 Lease 机制后再阅读写流程内容.写流程如下:
- Client 向 Master 节点询问哪一个 Chunk Server持有指定 Chunk的 Lease以及其他副本的位置,如果没有就选择其中一个副本建一个 Lease
- Master 节点将主 Chunk 的标识符以及其他副本的位置返回给 Client,Client 缓存这些数据.
- 只有主 Chunk 不可用或主 Chunk 表明其不持有 Lease,Client 才需要重新跟 Master 节点联系.
- Client 把数据推送到所有副本上.
- Client可以以任意的顺序推送数据
- Chunk Server 收到数据并保存在它的内部 LRU 缓存中
- 当所有的副本都确认接收到了数据,客户机发送写请求到主 Chunk 服务器
- 这个写请求标识了早前所有推送到副本上的数据
- 主 Chunk 为接收到的所有操作分配连续的序列号,并顺序执行这些操作,来更新自己(这个 Chunk)
- 主 Chunk 把写请传递到所有的二级副本上.每个二级副本依照主 Chunk 分配的序列号以相同的顺序执行这些操作.
- 所有的二级副本回复主 Chunk.
- 主 Chunk 服务器回复客户机.
- 任何副本产生的任何错误都会返回给 Client
- 客户端通过重复执行失败的操作来处理错误
注意: 对多个 Chunk 的写操作会将其分割成独立的,也就是以 Chunk 为单位进行写操作
3. 数据流动
为了提高效率, GFS 将数据流和控制流分开.控制流就是写流程中除第 3 步外的所有.数据流采取了一种特殊的方法进行推送,目标
是充分利用每台机器的带宽,避免网络瓶颈和高延时的连接,最小化推送所有数据的延时。
GFS 采用链式传输数据,如下图所示,数据流不会直接传输所有到目标机器,而是按照距离远近以链式方式传输.
所有 Chunk Server 使用基于 TCP 连接的、管道式数据推送方式来最小化延迟.接收到数据后立刻向前推送不会降低接收的速度.在没有网络拥塞的情况下,传送 B 个字节的数据到 N 个副本的理想时间是 B/T + RL, T 是网速,L 是在两台机器数据传输的延迟.通常情况下,我们的网速为 100Mbps,L 将远小于 1ms,因此 1GB 的数据在理想情况下 80ms 左右就能分发出去,而使用主从模式耗时会随副本数量线性增长.
4. Chunk 位置选择
副本位置选择在任何存储系统中都是非常重要的事情, Ceph 的 Crush, 一致性 hash都是解决这个问题的常用方法.
Chunk 副本位置选择的策略服务两大目标:最大化数据可靠性和可用性, 最大化网络带宽利用率.为了实现这个目标,仅仅是在多台机器上分别存储这些副本是不够的,这只能预防硬盘损坏或者机器失效带来的影响,以及最大化每台机器的网络带宽利用率。我们必须在多个机架间分布储存Chunk的副本。这保证Chunk的一些副本在整个机架被破坏或掉线(比如,共享资源,如电源或者网络交换机造成的问题)的情况下依然存在且保持可用状态。这还意味着在网络流量方面,尤其是针对 Chunk 的读操作,能够有效利用多个机架的整合带宽。另一方面,写操作必须和多个机架上的设备进行网络通信,但是这个代价是我们愿意付出的。
Chunk 位置选择发生在三种场景下:Chunk 创建, 重新复制和重新负载均衡.
a. Chunk 创建
当 Master 节点创建一个 Chunk 时会考虑几个因素:
- 希望在低于平均磁盘使用率的 Chunk 服务器上存储新的副本.这样可以平衡 Chunk Server 之间的磁盘使用率.
- 希望限制在每个 Chunk Server 上"最近"的 Chunk 创建操作的次数,更加平均的在所有Chunk Server 上创建新 Chunk.因为在绝大多数为追加的工作模式下,Chunk 创建成功几乎成为只读的了.
- 希望将 Chunk 的副本分布在多个机架之间.甚至机房或 IDC.这样既能提高数据可靠性,同时消除了单个机架对网络的限制.
具体的实现论文中没有描述, 不过由于有中心化的 Master 节点所以任何实现方法都是可用的.而不用考虑 Crush 或一致性 hash.这两种是在不同实现下的不同选择
b. Chunk 复制
当 Chunk 的有效副本数量少于指定数量时,Master 节点会重新复制它(这个信息会在 Master 启动时由 Chunk Server 上报,并由单一 Master 节点和 Chunk Server 心跳来保持是最新). 有以下的原因:
- Chunk Server 不可用
- Chunk Server 报告它存储的一个副本损坏了
- Chunk Server 的磁盘错误不可用
- Chunk 副本数量提升了.
Chunk 复制的策略和创建操作类似, 优先选择优先级最高的 Chunk(丢失副本数量更多或被访问的更频繁的 Chunk),选择策略如下:
- 平衡硬盘使用率
- 限制同一台 Chunk Server 上正在进行的克隆操作的数量
- 在机架间分布副本
- 限制整体复制操作的数量以此保证不会对正常服务造成影响
c. Chunk 重新负载均衡
Master 会周期性的对副本进行重新负载均衡: 它检查当前的副本分布情况,然后移动副本以便更好的利用磁盘空间,更有效的进行负载均衡.
由于GFS 有中心化的 Master 节点,所以任何Chunk 的迁移都不会对正常服务造成影响,同时 Chunk 管理策略的变更也不会对现有服务造成影响.
5. Namespace 和锁
使用 Namespace 可以将很多互斥的操作隔离,在保证安全性的同时提高并发.
不同于许多传统文件系统,GFS 的 namespace 就是一个全路径和元数据映射关系的查找表。利用前缀压缩,这个表可以高效的存储在内存中。在存储名称空间的树型结构上,每个节点(绝对路径的文件名或绝对路径的目录名)都有一个关联的读写锁。对一个节点进行写操作时首先会获取上级节点的读锁以及本节点的读写锁,这样就可以支持对同一目录的并行目录.
因为名称空间可能有很多节点,读写锁采用惰性分配策略,在不再使用的时候立刻被删除。同样,锁的获取也要依据一个全局一致的顺序来避免死锁:首先按名称空间的层次排序,在同一个层次内按字典顺序排序。
总结
GFS 是经典且经历近 20 年仍不过时的一个大文件分布式存储系统, 在 Google File System 对 GFS 的设计大概和细节都有详细的描述,本文只选择了一些重点和自己感兴趣的地方进行了记录.我认为深入学习一下 GFS 是有积极意义的.同时还有一些章节在本文中没有体现,在接下来的学习工作生涯中遇到相似的问题会继续在本文中更新.限于本文的作者水平,文中的错误在所难免,恳请大家批评指正.
在 3.3 节的数据流动中, 经过计算让我对链式传输和主从传输有了更深入的认识,也许计算机中计算才是最重要的.
引用
- google: The Google File System
- xybaby: 典型分布式系统分析: GFS