GFS论文的总结和摘要

225 阅读21分钟

什么是文件系统?

文件系统是干嘛的? 存储文件的系统。

无论是KV数据,还是配置文件,或是数据库中的数据或者关系组,在操作系统底层都是以文件的方式进行存储,为了统一管理一个大型系统所有需要持久化的文件,我们需要一个文件系统集中提供对文件的存储等操作,比如GFS,HDFS,......其中GFS是最著名的分布式文件系统,具备了大规模、可扩展、适配大文件、自动运维等高级特性。

常用的操作系统文件是一个单机文件系统,存在存储容量和读取性能的瓶颈,把文件分散储存在多台机器上构成了分布式文件系统。

文件系统的接口

文件系统应该具有的接口:

创建(create) 删除(delete) 打开(open) 关闭(close) 读取(read) 写入(write)

其扩展接口:

生产快照(snapshot) 修改(update) 追加(append)

GFS的整体架构

对于GFS来说一共有三个组件节点分别是GFS client、GFS master、 GFS chunkserver。

  • GFS client: 维持专用的接口,与应用交互
  • GFS master 维护元数据,统一管理chunk的位置与chunkserver的租约
  • GFS chunkserver: 存储数据的节点

GFS的存储设计

考虑到文件可能非常大,并且大小不均匀,GFS没有以文件为单位来进行存储,而是将文件分割成一个个chunk来进行存储,GFS把每个chunk分割成64MB.这个思路在分布式领域很常见,几乎所有分布式文件存储系统都会选择把文件或者数据分割成相同大小的数据块来进行存储。

为什么采用64MB呢?使用到GFS的系统存储的文件都会偏大 GB 为单位,所以较大的chunk可以有效的减少系统内部的寻址和交互次数。大的chunk意味着cliend可能在一个chunk上执行多次操作,可能发现需要多次操作的数据存在一个chunk上的概率比较大,复用tcp连接节省网络开销,另外更大的chunk可以减少chunk的数量从而减少元数据存储的开销,相当于洁身了系统内最珍贵的资源。因为在gfs中 master 节点的资源是最珍贵的一般瓶颈就在这里。

其中chunk在chunkserver中的分布:

GFS的 master 设计

GFS采用单个master节点用来存储文件系统的三类元数据,由此可以简单推断出GFS读取文件的流程了:文件名 - 》获取文件对于的所有chunk - 》计算当前文件数据在哪个具体chunk上 -》依次读取chunkserver上

  • 所有文件和chunk的namespace【持久化】
  • 文件到chunk的映射【持久化】
  • 每个chunk的位置【不持久化】

为什么采用单节点 master 来统一管理元数据?

为什么chunk的位置不做持久化?

因为master在重启的时候,可以从各个chunkserver处收集chunk的位置信息

GFS采用了一系列的措施来保证单master节点不会成为整个系统的瓶颈。

  • GFS所有的数据流不经过master节点,而是直接由client与chunkserver交互
  • GFS把控制流和数据流分离,只有控制流才会经过master

gfs论文的数据流和控制流分离是什么意思?

在计算机科学中,数据流和控制流是程序执行过程中的两个重要概念。数据流指的是数据在程序中的传递和处理过程,而控制流则是指程序执行的顺序和流程控制(如循环、条件分支等)。

在GFS(Google文件系统)的论文中,提到数据流和控制流分离,主要是指在分布式文件系统中,数据的存储和检索过程与文件系统的控制和管理过程是分开的。这种分离可以提高系统的可扩展性和可靠性,因为它允许系统在不同的层面上进行优化和扩展。

具体来说,数据流指的是数据在系统中的复制、备份、恢复等操作,这些操作关注的是如何确保数据的持久性和一致性。而控制流则涉及到文件的元数据管理、命名空间的维护、访问控制等,这些操作关注的是如何有效地管理和调度数据流。

在GFS的设计中,数据流和控制流的分离体现在以下几个方面:

  1. 数据复制和分布:GFS通过数据块的复制和分布来提高数据的可靠性和可用性。这些数据块的复制和恢复过程是自动进行的,不需要用户或管理员的干预,这样就将数据流的复杂性与控制流的管理逻辑分开。
  2. 元数据管理:GFS使用一个称为Master的组件来管理文件系统的元数据,包括文件和目录的命名空间、访问控制信息等。Master组件负责处理所有的控制流操作,如文件的创建、删除、重命名等,而实际的数据块则存储在Chubby锁服务和多个Chunk服务器上。
  3. 故障检测和恢复:GFS能够检测到Chunk服务器的故障,并通过数据流的自动复制和恢复来保证数据的完整性。这个过程是自动的,不需要Master组件的直接参与,从而进一步分离了数据流和控制流。

通过这种分离,GFS能够更好地处理大规模数据的存储和检索,同时保持文件系统管理的高效性和灵活性。这种设计使得GFS可以支持大量的并发操作,提高了整个系统的性能和可靠性

  • GFS的client一般都会缓存master节点的元数据,尽量不访问master节点
  • 为了避免master节点的内存成为系统的瓶颈,GFS采用了一系列手段来进行优化,比如说增大chunk的大小以节省chunk的数量,对元数据定制化压缩

一个64MB的chunk,它的元数据小于64B(8字节)

假设一个文件存储三份,每份的元数据都是独立的。那么1TB数据对应的元数据大小为:

1TB3/64MB64B = 3145728B = 3072KB =3MB

GFS的高可用设计

高可用的问题比较通用的解法就是使用共识算法来进行,比如raft、paxos但GFS诞生的时候,共识算法不像现在这么成熟,所以GFS借鉴了主备的思想,为系统的 元数据和文件数据都单独设计了高可用方案。因为Google的文件量很大,所以GFS的机器总数可能非常的多,从而个别机器宕机的发生 会十分频繁。所以面对节点宕机之类的小问题,GFS应能自动解决,即自动切换主备。

同时对于高可用的定义也要区分是否是逻辑上的高可用还是物理上的高可用。

逻辑上的高可用

逻辑上的高可用主要关注软件层面的设计和实现,以确保服务的连续性和数据的完整性。以下是一些逻辑上实现高可用的策略:

  1. 冗余设计:通过在软件层面实现冗余,比如使用多个应用实例、数据库副本等,确保当一个实例或节点出现故障时,另一个可以立即接管工作。
  2. 负载均衡:使用负载均衡技术分散请求到多个服务实例,不仅可以提高性能,还可以在一个实例故障时平滑过渡到其他实例。
  3. 故障转移和恢复:实现自动故障检测和故障转移机制,当主服务实例出现故障时,能够迅速切换到备用实例,同时在故障恢复后自动恢复到主实例。
  4. 数据一致性保证:采用事务管理、数据复制、分布式锁等技术,确保数据在多个节点间保持一致性,避免数据丢失或损坏。
  5. 状态管理和持久化:确保系统状态能够在故障后恢复,例如通过状态机、检查点、持久化存储等技术。

物理上的高可用

物理上的高可用关注硬件和基础设施的可靠性,以减少单点故障带来的风险。以下是一些物理上实现高可用的策略:

  1. 硬件冗余:使用双电源、RAID 存储、多网络接口等硬件冗余技术,确保当一个硬件组件故障时,系统仍能继续运行。
  2. 数据中心地理分布:在不同的地理位置部署数据中心,避免自然灾害、电力故障等影响整个服务的可用性。
  3. 网络冗余:通过多个互联网服务提供商(ISP)和多路径网络连接,确保网络连接的稳定性和可靠性。
  4. 定期维护和监控:实施定期的硬件检查和维护,以及实时监控系统性能和健康状况,及时发现并解决潜在问题。
  5. 灾难恢复计划:制定详细的灾难恢复计划,包括数据备份、关键组件的快速替换流程等,以快速从灾难性事件中恢复服务

RAID知识补充:

  • RAID 0:条带化(数据分块)但没有冗余,提供较高的读写性能。
  • RAID 1:镜像,数据完全复制到另一个驱动器,提供容错能力。
  • RAID 5:条带化加分布式奇偶校验,提供数据冗余和读取性能。
  • RAID 6:类似于RAID 5,但提供更高级别的容错能力。
  • RAID 10:RAID 1+0,将RAID 1镜像组合成RAID 0条带化,提供较高的容错能力和读写性能。
  • RAID 50:RAID 5组合成RAID 0,提供较高的性能和容错能力。
  • RAID 60:RAID 6组合成RAID 0,提供更高级别的性能和容错能力。

对于 master 的高可用GSF如下设计:

  • master的三类元数据中,namespace和文件与chunk的对应关系,因为只在master中存在, 是必须要持久化的,也自然是要保证其高可用的。
  • GFS在正在使用的master称为primary master。在primary master之外,GFS还维持一个 shadow master作为备份
  • Master在正常运行时,对元数据做的所有修改操作,都要先记录日志(WAL),再真正去 修改内存中的元数据
  • primary master会实时向shadow master同步WAL,只有shadow master同步日志完成,元 数据修改操作才算成功。

类似于mysql两阶段提交,master 的高可用机制就和MySQL的主备机制非常像。

对于chunk的高可用设计:

  • 文件是被拆为一个个chunk来进行存储的,每个chunk都有三个副本。所以,文件数据的高可用 是以chunk为维度来保持的
  • 通过在master节点上来维护chunk主备信息
  • 对于chunk的写入只有等待所有副本写入才算一次真正写入
  • 如果一个chunk数据的chunkserver宕机,他所有上面的chunk另外两个副本依旧可以保存当前chunk数据,同时一段时间后还没恢复master节点就可以在另一个chunkserver节点重建副本始终保持副本节点在3个。
  • GFS维持每个chunk的校验和,读取时可以通过校验和进行数据的校验。如果校验和不匹配, chunkserver会反馈给master处理,master会选择其他副本进行读取,并重建此chunk副本。
  • 为了减少对master的压力,GFS采用了一种租约(Lease)机制,把文件的读写权限下放给 某一个chunk副本。
  • Master可以把租约授权给某个chunk副本,我们把这个chunk副本称为primary,在租约生 效的一段时间内,对这个chunk的写操作直接由这个副本负责,租约的有效期一般为60秒。
  • 租约的主备只决定控制流走向,不影响数据流
  • 如果发现某个chunkserver的负载过高, 就会执行负载均衡操作,把chunk副本搬到另外的chunkserver上。当然,这里的“搬迁”操 作,实际上就是新建chunk和删除原chunk的操作
  • 新副本所在的chunkserver的资源利用率较低;最近创建的chunk副本不多,防止某个chunkserver突然增加大量副本成为热点
  • chunk的其他副本不能在同一机架。这里是为了保证机架或机房级别的高可用

GFS的读写流程

文件系统的读写流程其实本质上就是在探讨数据一致性问题,同时也要保证一定的性能。而对于GFS来说,其写入流程如下:

GFS的写入要等待三个副本都写入完成才能返回最终结果,其中GFS采用两种技术来写入:流水线技术、数据流与控制流分离技术

流水线技术,像下图这样,client会把文件数据发往离自 己最近的一个副本,无论它是否是主(是否持有租约)。这 个副本在接收到数据后,就立刻向其他副本转发(一边接收, 一边转发)。这样就控制了数据的流向,节省了网络传输代价。

与流水线技术对应的是普通的主备同步,数据是从Client到主, 再从主到备这样单向流动, 比如S1S2S3三个chunkserver,Client在北京,S1是主,在上海; S2S3是备,在北京。

主备同步:Client → S1 → S2,S1 → S3,两次跨地域传输

流水线同步:Client → S2/S3 → S1,一次跨地域传输

而数据流和控制流分离,意味着GFS对一致性的保证可以不受 数据同步的干扰,直观理解起来就是,数据量很大的数据流大家有力出力,全部 都动员起来;而数据量小的控制流,则由持有租约的 chunkserver自己决定,来单独负责写入的一致性保证。从而 达到性能和一致性的均衡

GFS的写入流程:

1、Client向Master询问要写入chunk的租约在哪个chunkserver 上(Primary Replica),以及其他副本(Secondary Replicas)的位 置(通常Client中直接就有缓存)

2、Client将数据推送到所有的副本上,这一步就会用到流水线 技术,也是写入过程中唯一的数据流操作。

3、确认所有副本都收到了数据之后,Client发送正式写入的请 求到 Primary ****Replica 。Primary Replica接收到这个请求后,会对这个Chunk上所有的操作排序,然后按照顺序执行写入

Primary ****Replica 唯一确定写入顺序,保证副本 一致性

  1. 避免冲突更新:当多个客户端同时向系统发送写入请求时,如果没有一个统一的写入顺序,可能会导致数据冲突。例如,两个客户端可能几乎同时更新同一个数据项,而这些更新可能会相互覆盖。通过让主副本来确定写入顺序,可以避免这种冲突,确保每个写入请求都按照一定的顺序被处理。
  2. 顺序一致性:在分布式系统中,客户端期望看到的是顺序一致性,即操作的执行顺序与它们被发出的顺序相同。主副本通过按照接收到的顺序执行写入操作,然后将这些操作的日志复制到其他副本,从而保证了跨副本的顺序一致性。
  3. 简化冲突解决:即使在发生冲突的情况下,有一个明确的写入顺序也可以帮助系统更容易地解决冲突。例如,系统可以通过“最后写入优先”(last write wins)的策略来解决冲突,即保留最新的写入操作,并丢弃之前的冲突操作。
  4. 提高效率:如果每个副本都独立地处理写入请求,那么系统需要复杂的机制来协调和合并这些更改,这会增加系统的复杂性和操作的延迟。通过让主副本来处理所有写入请求,系统可以减少协调的开销,并提高整体的效率。
  5. 故障恢复:在发生故障时,系统需要从备份中恢复数据。如果所有写入操作都通过主副本来处理,那么系统可以更容易地确定哪些操作已经成功提交,并在恢复过程中重新应用这些操作,从而确保数据的一致性。
  6. 一致性模型:许多分布式系统采用强一致性或最终一致性模型。主副本的写入顺序机制是实现这些一致性模型的关键。强一致性要求所有副本在任何时候都保持一致的状态,而最终一致性允许短暂的不一致,但最终所有副本都会达到一致状态。主副本通过有序地传播更改来支持这些一致性模型。

4、Primary Replica把Chunk写入的顺序同步给Secondary Replica

5、所有的Secondary Replica返回Primary Replica写入完成

6、Primary Replica返回写入结果给Client,写入成功返回响应,没成功重复第2步

而对于GFS的写入操作来说分为两种:改写追加写,改写可以完全适配上面描述的写入的步骤,重复执行改写也不会产生副本之间的不一致,但改写的问题在于一个改写操作可能涉及到多个chunk。 而如果部分chunk成功,部分chunk失败,我们读到的 文件就是不正确的,改写大概率是一个分布式操作,如果要保证改写的强一 致性,代价就要大很多了比如使用2pc。

而对于追加写来说顺序追加, 因为顺序写入可以更高效地利用磁盘I/O和网络带宽。当客户端需要追加数据到文件时,GFS会将这些数据写入到文件的末尾。由于追加操作是顺序的,它们可以最小化磁盘寻址时间,并提高写入性能

数据流与控制流分离技术:

GFS的一致性模型

我们在讨论读写流程的时候,已经明确了维持副本间一致的基本方法,可以保证在单个写 入的过程中,不会出现副本之间的不一致,但在实际应用中,因为有多个Client,我们的写入往往是并发执行的,这会带来副本间不 一致的风险。GFS把文件数据的一致性大体上分为三个层次:inconsistentconsistentdefined

inconsistent: 不一致

consistent: 一致的

defined: 已定义的。文件发生了修改操作后,读取是一致的,且Client可以看到最新修改的内容。(在consistent的基础上还能与用户最新的写入保持一致)

我理解的defined与consistent的区别在于

consitent在并发改写chunk数据的时候,可能在用户层面上出现不一致,比如说chunkA上的A:10数据,并发情况出现更改A,一个+1一个-10;在+1和-10用户这里可能出现结果1;出现了与用户层面的逻辑不一致,但是总体是一致的。

defined在并发读写的时候+1用户出现11结果,-10用户出现0,再一次查询时最终结果为1.

串行改写成功:defined。因为所有副本都完成改写后才能返回成功,并且重复执行改写 也不会产生副本间不一致,所以串行改写成功数据是defined。

写入失败:inconsistent。这通常发生在重试了一定次数仍无法在所有副本都写入成功时, 意味着大概率有个副本宕机了,这种情况下一定是不一致的,Client也不会返回成功

并发改写成功:consistent but undefined。对于单个改写操作而言,成功就意味着副本 间是一致的。但并发改写操作可能会涉及多个chunk,不同chunk对改写的执行顺序不一定 相同,而这有可能造成应用读取不到预期的结果。

追加写成功: defined interspersed with inconsistent(已定义但有可能存在副本间不一致)

GFS为了实现追加的一致性特性,对追加做了一些额外的限制,单次append的大小不超过64MB。如果文件最后一个chunk的大小不足以提供此次追加所需空间,则把此空间用padding填满,然后 新增chunk进行append,这样,每次append都会限制在一个chunk上,从而可以保证追加操作的原子性,在并发执行时也可以 保证Client读取符合最新追加的结果。 并且,重复追加的问题相对来讲很好解决,比如文件原有的值是‘ABC’,追加‘DEF’。有的副本第一次 失败再重复执行就是‘ABCDEF’,而两次都正确的副本是‘ABCDEFDEF’。但我们有很多手段可以在读取 时仅读到一个‘DEF’,比如记录文件长度,比如各副本定期校验。

GFS的一致性模型是通过以下四种方式保证一致性:

  • 对一个chunk所有副本的写入顺序都是一致的。这是由控制流和数据流分离技术实现的,控制流都是由 primary 发出,而副本的写入顺序也是由primary到 secondary
  • 使用chunk版本号来检测chunk副本是否出现过宕机。失效的副本不会再进行写入操作, master不会再记录这个副本的信息(等Client刷缓存时同步),GC程序会自动回收这些副本
  • master会定期检查chunk副本的checksum来确认其是否正确。
  • GFS推荐应用更多地使用追加来达到更高的一致性

总结

对于外部应用而言,只需要把要读写的文件的相关信息传入GFS的Client,Client就会自动进行读写服 务。除此之外,文件在GFS中是怎样保存的、会不会丢失(高可用性),文件总量快速增长怎么办、 GFS容量会不会不够用(可扩展性)等问题,使用GFS的应用都不用关心,因为 GFS内部的存储设计 和高可用设计,都可以基本保证对外部应用的读取没有功能上的影响(透明

但有一点需要注意:对GFS内部的数据,只有通过GFS的接口,也就是Client来读写,才能确保得到你 想要的结果(或者说“正确的”结果)。因为GFS不仅会把文件拆成64MB的chunk分开存储,它们的位 置全是自动分布的;并且GFS在读写的过程中还会对文件数据进行一些存储上的改动,这些改动(比 如追加时的padding)在通过Client读取时是不可见的,但是又确确实实存在于GFS的服务器上

GFS的快照(Snapshot)机制通过采用COW Copy On Write 写时复制 ), 同时对于GFS来说实现Crash恢复是通过快照+checkpoint log结合进行数据恢复的,也就是说在进行snapshots的时候在log日志上标记checkpoint点,后续恢复直接使用当前点来进行日志恢复。

GFS的 垃圾回收 ( GC )机制,不立刻清除chunk的物理存储,而是修改文件的元数据,把文件名改为一个包含删除时间戳 的、隐藏的名字。Master会定期对namespace元数据进行扫描,当发现文件删除超过3天(可配置),就会把这个元数据删除掉。Master定期扫描各Chunkserver汇报的chunk集合,当发现没有对应文件元数据的chunk (不被任何文件包含的chunk)时,Chunkserver就可以把这些chunk真正删除掉了