GFS作为谷歌三驾马车之一的存储,对于分布式系统的发展有很大的作用,大致整理一下其中的内容
目录
假如chunkserver 返回了一个比master存的chunk 版本更高的版本会怎么样
假如存所有chunk最新版本的chunkserver全下线了会怎么样
如果我们通过 GFS 的客户端要写入一部电影到 GFS,然后过一阵再读出来,我们都可以有哪些方式,来保障这个电影读取之后能够正常播放呢?
架构总览
- 主服务器 master
- 存储文件的组成 chunk
- 文件的存储节点 chunkserver
master
master的功能: master存储了三种主要类型的元数据: 文件和块命名空间,文件到块的映射,以及每个块的副本的位置。通过master才能确定chunk的位置,以及chunk的调度等操作也是master来分配的。
特点:单点存储。master实际上是一个单点,对外提供服务时只有一个节点,从这个角度来说并不能提供完全的高可用。
master的可用性提升方法:通过backup master (Master Replication 5.1.3)和 shadow master(只读访问)。
- backup master:只有backup master和master 都写入数据,才算完全的写入(同步写入)。
- shadow master主要是在master挂掉后给只读操作提供服务,避免服务不可用的,它是异步写入,所以master挂掉后shadow master的内容可能有部分滞后,考虑到刚好读到滞后内容的可能很小,所以可以容忍。
- 对master重启的描述:当它发生故障时,它几乎可以立即重新启动。如果它的机器或磁盘故障,GFS外部的监控基础设施会在其他拥有复制操作日志的地方启动一个新的master进程。客户端仅使用master的标准名称(例如,gfs-test),这是一个DNS别名,且如果master被分配到另一台机器上,它可以被修改。
持久化存储:master的前两种类型(文件和块命名空间,文件到块的映射)持久化存储到 operation log中,在backup master和shadow master中都进行存储。
恢复策略:在日积月累中operation log可能过多,此时依次加载过于耗费时间,因此采用 checkpoint 来做一个快照备份,根据checkpoint 和 checkpoint 之后的operation log,做一个全量备份+增量备份的恢复。
chunk相关的信息:在调用的时候,chunk相关信息全部加载在master的内存中,保证了查询速度。上述持久化的信息实际上没有存储chunkserver的位置,在master挂掉后,该信息如何获取——chunkserver会通过心跳定时上报信息,因此master可以直接获取。
chunkserver
chunk(块文件)的唯一存储位置,对chunk的写和读操作统一由chunkserver进行
chunkserver内的chunk是多副本的(一般为3),多副本里面会有一个主副本(chunk primary),由它来确定数据如何变更。primary 有一个租约(一般持续60s,假如在变更数据可以无限延长)来保证primary所在的chunkserver挂掉等不利影响
chunk
每个chunk最大大小为 64MB,chunk体现在chunkserver里面就是一个个目录下的文件
对于抽象的文件来说,文件的特定位置可以转换为具体的chunk。假如写入的内容过大,会转换为对多个chunk进行操作
每个chunk都有对应的版本号,master维护了最新的版本号,假如因为某些原因导致chunk的版本号滞后,master会对该chunk进行回收。 (因为这个版本号唯一决定了chunk是否是最新的,所以该数据有持久化存储到硬盘)
文件操作
具体的文件操作包括 创建(create)、删除(delete)、打开(open)、关闭(close)、读取(read)和写入(write),还有2个特定操作:追加写(record append) 和快照(snapshot)
读取:
如上所述,读取某个文件的特定位置会被转换为读取chunk,进而到chunkserver内进行读取
写:
写操作有几个流程:
- 与master交互获取chunk位置(之后与master无关了)
- 将数据拷贝到最近的chunkserver上
- 该chunkserver会继续进行数据拷贝到其他chunkserver上
- 直到所有chunkserver均获取该数据
这样操作是为了让数据流尽可能地避免到网络拓扑结构的高层,降低传输延迟,所以选择最近的那个chunkserver,而不是优先选择 chunk primary
每台机器把数据转发到网络拓扑上尚未收到数据的"最近的(closest)"机器 (3.2)
追加写:
追加写是在某个文件的后面写入内容,该操作可以保证是原子的。
假如写入的数据对该文件的最后一个chunk来说过大,那chunkserver会把最后一个chunk用空数据填满,然后返回让客户端在新的一个chunk上进行写入。
追加写操作是原子性的
为了降低追加写导致空数据填满chunk的可能:(例如追加写一个 63MB的数据,当前最后的chunk只有2MB,那最后一个chunk会填满62MB的数据,非常浪费)追加写单次最多只允许16MB的数据操作。
快照:
做快照操作,所有对应的chunkserver会在本地将chunk直接复制一份。(没有经过网络,速度最快。)后续应该会通过master慢慢调度到合适的chunkserver上。
文件写入的一致性保证
GFS的一致性保证非常脆弱,它只能做到“至少一次”的承诺,所以需要客户端使用唯一性id来去重。弱化唯一性的好处就是chunkserver本身的设计不用太复杂。
总的来看,不能保证chunk数据“恰好一次”,也不能确定其出现的顺序。
论文里面定义了2个概念 一致(consistent)和 被定义(defined)
- 如果所有的客户端不论从文件的哪个副本读取,都能读到相同的数据,那么这个文件区域就是一致(consistent) 的。
- 如果一个区域(region)在文件变更后是一致的,并且客户端将会看到变更写入的全部内容,那么这个区域就被定义(defined) 了
至少一次:在追加写的过程中,例如,写入A时某个副本写失败了,那此时会进行重试,重试时,之前写入成功的副本不变,只确保写成功操作是原子的(即不会写一半),所以可能出现如下的情况:如图,在Q、P chunkserver上,A 出现了2次
如果一个记录追加在任意副本上失败,客户端会重试操作。因此,相同块的副本可能包含不同的数据,这些数据包含相同记录的完整重复或部分重复。GFS 不保证所有的副本每个字节都相同。它只保证数据作为一个原子性单位,至少被写入一次。这个属性很容易从简单的观察中得出,为了使操作报告成功,数据必须在某个块的所有副本上相同的偏移量写入。此外,在这之后,所有的副本都至少与记录结尾一样长,因此,即使一个不同的副本成为 primary,那么未来的任何记录都会被分配更高的偏移量或一个不同的块。在我们的一致性保证方面,成功的记录追加将数据写入的区域是定义的(defined)(因此也是一致的),而中间的区域是不一致的(因此也是未定义的)。我们的应用程序可以处理不一致的区域
chunk primary
chunk primary是多个副本里面决定如何执行提交顺序的(例如有多个client同时发起了追加写),并同步到其他副本。从这个角度来说,提交顺序是一致的。
chunk primary持有一个租约,如上所述,通常60s,变更状态下可以延长。
master 无法联通的措施
假如master无法ping通chunk primary,会怎么做呢——master会等待租约到期后,再重新指定chunk primary。 之所以不立刻指定新的chunk primary,是因为可能旧的chunk primary 并没有挂掉,只是和master联不通而已,此时贸然指定新的primary,可能导致有2个chunk primary同时存在,引发脑裂(split brain)。
追加写和随机写的对比
GFS 极力推荐用追加写,追加写是原子性的,并且追加对于很多大数据存储(例如谷歌的搜索内容存储)都是更为适用的。
随机写操作的问题
GFS的随机写有可能导致读取是不确定(defined)的:例如A和B同时对chunk1 和 chunk2进行写入,t1时刻,A写入chunk1,B写入chunk2,t2时刻:A写入chunk2,B写入chunk1。此时就无法读取到一个完整的A写入的数据或者B写入的数据了。
由此可见,GFS 只能做到单 chunk 写入的正确性。 ——aiwamisaki
追加写的两种用法:
- 单写者(Writer):单个客户端执行追加写操作,可以保证写入顺序(不过重试机制可能导致chunk的顺序不一致)
- 多写者:对同一个文件同时有多个写者进行追加写,可以实现生产者和消费者队列
补充
客户端的缓存
由于客户端缓存了块信息,所以它们可能会在信息刷新之前从一个过期的块进行读取。这个窗口受限于缓存条目的超时和文件的下一次打开,文件重新打开会清除缓存中关于这个文件的所有块信息。此外,因为大多数文件是仅追加(append-only)的,一个过期的副本通常返回一个过早结束的块而不是过期的数据。当一个读者(reader)重新连接(retry)并联系(contact)master时,它会立即得到当前的块位置
所以客户端缓存过时的时候,一般不会从过期的副本中获取过期的数据。
降低网络传输限制的办法
- 通过每台机器把数据转发到网络拓扑上尚未收到数据的"最近的(closest)"机器来解决网络传输问题。通过避免经过更高层的交换机来降低传输延迟
- snapshot,本地复制,没有网络带宽限制
- 通过管道化(pipeline)来最小化时延。一旦一个 chunkserver 收到某些数据,它就立即开始转发
chunk的调度方式
master会根据chunk的位置来进行放置
块副本放置策略有两个目标:最大化数据可靠性(reliability)与可用性(availability)和最大化网络带宽利用率 (4.2)
假如有chunkserver挂了,就进行复制
假如接入新的chunkserver,逐渐将chunk移过去,避免一批次大量移动带来的带宽问题。
假如chunkserver 返回了一个比master存的chunk 版本更高的版本会怎么样
MIT 6.824的课程里面,教授的回答是,有可能master会更新自己存储的版本号,不过论文没有明确说明这一点。
假如存所有chunk最新版本的chunkserver全下线了会怎么样
master会等待或者返回报错给client。有可能是全部集群重启,所以可能稍后chunkserver又连上了,但是也可能数据丢失找不回来了。
如果我们通过 GFS 的客户端要写入一部电影到 GFS,然后过一阵再读出来,我们都可以有哪些方式,来保障这个电影读取之后能够正常播放呢?
考虑到电影文件必然是流式传输,不可能全部获取文件再进行排列,可以将文件分为若干个16MB(追加写的允许范围内)大小的小文件,通过追加写操作来将小文件上传,记录对应小文件的顺序,然后分组拉取组成视频流。这样可以避免追加写写失败时重试导致的存储数据顺序与上传顺序不一致的问题。
且写者只能有一个,避免多写者导致的并发问题。
参考文章
- 《The Google File System》
- 【译文】The Google File System 经典的分布式文件存储系统
- 《The Google File System》论文翻译(GFS-SOSP2003)
- 极客时间-大数据经典论文解读 徐文浩
- GFS的分布式哲学:HDFS的一致性成就,归功于我的失败……
- MIT 6.824: Distributed Systems - Lecture 3: GFS
推荐博客
- 《aiwamisaki:Google 分布式系统:GFS,BigTable,MapReduce,Spanner,Chubby》