表面上post时间是2019-11-09,但实际写作时间是2023-11-19
序
好,那么这一次我们来看GFS论文。
从上面展示的咕咕时间对比可以看出,尽管很早之前就想要了解分布式系统在工业界首次应用的典型,尽管也确实花了点时间读了论文和众多的解读资料,但是始终没有(时间)信心做自己的解读和归纳;今天终于决定尝试这么做的动机是,上周看到Oceanbase 1.0架构的文档,从组件命名中发现 1.0 之前的OB是参考了GFS的架构:比如ChunkServer、单点的UpdateServer(GFS中的master)。因此决定回头捡一下相关的知识,这次就不再逐段翻译了,只抽取一些重点进行解读。
首先从摘要我们能了解到的信息有:GFS是一个能提供 fault tolerant 的分布式文件系统,并且是通过数百数千个普通服务器节点组件起来的,具有很高的可扩展性。这一点是非常有开创意义的,这意味着我们可以不断增加节点来提高集群。// 但是当时的GFS是否还是单点更新的?先往后看。
Intro部分介绍了GFS设计时需要考虑的几个因素,大部分都是面向谷歌的具体应用场景,但其中的第一点是至今仍然通用的:认为集群中有节点宕机/故障是经常出现的情况。这意味着系统需要将节点的故障和恢复妥善处理好,使得整体服务不受个别节点的故障影响。GFS具体如何做到这一点,是我们今天关注的重点。
顺带一提,在传统数据库架构中,容错通常是通过主备节点的方式来完成,比如Oracle/MySQL /*TODO:replica,*/ 主备节点之间通过日志同步的方式来实现数据同步,这种架构是不能将节点故障作为常态处理的。比如在最大保护模式下,备节点的故障也会使得主节点停止写入;最大性能模式下主节点故障,由于日志同步是异步的,切换到备节点可能出现丢数据的情况,均无法做到无缝切换节点提供服务,无法将节点故障看作“常态”(不如说这些架构是基于“节点不太可能故障”来设计的,比如之前Oracle通常运行在昂贵的小型机上//几百万RMB一台)
GFS提供了类似一般文件系统的接口,保证多个客户端并发的append请求可以原子完成;支持快照功能,能够低开销地创建文件或目录的快照。这些功能中,我们关注GFS如何实现无锁的并发追加写,以及如何创建快照。
虽然支持随机写,但 GFS 设计面向的负载为追加写(append)居多
GFS 架构
GFS 由一个 master 和若干 chunkserver 构成。
master 上存储各种meta信息,包含三种:1.namespace,2.文件名到chunk的映射关系(比如file1存储在chunk 1、3、4中),3.chunk的位置等(比如chunk1 在 server 10.11.23.1、chunk 4在 10.11.23.4上),前二者需要持久化到日志,chunk的位置不持久化,而是通过 master 轮询所有 chunkserver 来获取;master 也负责维护chunk的租约、迁移以及回收等工作;
文中提到不持久化 chunk 的位置而是通过定期轮询的方式,能够省略每次节点写入后、重启后、改名后都与 master 进行同步来更新信息。总而言之,降低了系统的复杂性,但代价是 master 上信息更新的延迟相对较高,若对已经变动的 chunkserver 发送读写请求,就需要等待 master 更新最新的 chunkserver 信息后才能继续;不过 GFS 的设计目标之一就是优先追求高带宽而不注重请求的延迟,因此他们可以采用这种方式。
chunkserver 则将 chunk 作为普通的linux文件存储在本地,同时每个chunk也设置了多份备份作为容错。GFS 保证 chunkserver 上存储的文件块是一致的 /*TODO:这里的一致性是什么程度的保证?*/ ,因此读取操作读任意一个副本即可;写入操作为了保证所有副本的一致,GFS会由master挑选出某个 replica 作为 primary 来组织写入操作的顺序。下文将对读写操作展开进行详细描述。
此外,由于使用了 master 这样的单点组件,为了避免单点故障,GFS要求修改操作的日志必须在本地和远程存储上都成功写入才能提交。以便于在master进程(或所在节点)故障时,能通过其他副本的备份拉起新的 master。
读取
读操作比较简单,大致流程为:client向 master 发送需要读的chunk和index,master返回对应 chunkserver的位置信息;随后 client 直接与 chunkserver 交互完成读取。同时client还可以缓存 chunkserver 的 meta 信息,在租约过期前不用再向 master 发送请求,进一步减少 master 的负载。
写入
master 给其中一个 replica 授予租约(通常 60s)作为 primary,由 primary 组织写入操作的顺序,其他replica 按照这个顺序应用。
这里简要描述写入流程:
-
client 向 master 询问持有租约的chunkserver,master 返回 primary 和 其他 secondary replica 的信息。
-
client 向所有 replica 推送数据;replica 收到后会缓存在 LRU buffer,全部 ack 后,clint 再向 primary 发送写入请求。primary 为写入操作定序(可能就是按照它收到的请求的顺序,虽然可能来自许多客户端的并发请求),并将修改应用到本地。
-
primary 将写请求转发给 secondary,secondary 按照 primary 指定的顺序应用;完成后向 primary 返回 ack。
-
primary 收到所有 ack 后向 client 返回成功。如果写入 secondary 发生错误,则认为这次写入失败,这部分文件处于不一致状态。客户端会重试这次写入操作。 // 不一致的文件块 如何处理?等待GC?
如果 primary 写入 fail, 那么当作本次写入没有发生。primary 写入的完成相当于事务的提交点。
// 并不尝试更新 stale replica 的数据(如果更新数据要处理很多情况,比如要判断谁的数据是最新、补数据过程也失败后如何清理未能完整写入的数据、补数据过程中的如果出现写入要保证在新副本上也同步);而GFS采取的方法是GC stale chunk,即直接丢弃 stale chunk,然后再利用 chunk replication 的机制来补齐副本。 // consistent // define // 只要判断是 define 就行了; inconsistent 似乎是允许存在的?
// 画图,展示 define 和重试得到 define 的场景
GFS的高可用
总结
回答文章开头提出的几个问题:
-
GFS如何应对节点故障、使得系统在集群节点发生故障的情况下依然提供正常服务的?
-
GFS如何实现无锁的并发写入?
-
如何生成快照
GFS保证高可用的机制
- 多副本
- xx
对应的缺点:
讨论
对GFS中一些机制设计理念的讨论,思考相应的设计在分布式数据库场景中有何体现。
-
master不持有chunk信息;
这是GFS的应用场景对延迟要求低才能采用的设计,确实简化了GFS系统的复杂度。不过这意味着:如果 client 某个请求使用了 master 上过期的 chunk 位置信息,在错误的 chunkserver 上找不到数据,需要等到下一次 HeartBeat 之后 master 才能获取正确的 chunk 存放位置,这次请求最坏情况下需要在正常流程的延迟基础上,额外等待一次心跳周期的时间。对于TP数据库而言,这种情况是我们不愿见到的。 -
master写入操作日志需要所有副本的写入成功;
虽然使用了多副本作为容错,但因为需要“写所有副本成功”,因此任何一个 master 副本节点/网络的故障也会导致停写;此时master将只能支持只读操作。不过后来有类似的“写所有”机制的数据库(好像是FDB还是Aruro?回头看看) -
master 上集群的信息能够全部缓存于内存中;
sec 6 的实验,在一个300+台 chunkserver、73w文件 55TB数据的集群中,master 内存中的 meta 信息也之后几十MB。 这是因为 GFS 使用了非常大的 chunk size(64MB),因此 master 需要记录的 chunk 位置信息非常的少。 如果在数据库中,GFS中的 chunk 大概对应于表的分区?// 那分区信息似乎是并不太多;有空了解一下