last modified: 2020-04-06 12:25
GFS由2种类型节点组成,master和chunk-server. master保存系统的元数据信息,chunk-server存储实际数据文件。
数据在GFS中被切分为chunk,chunk保存在chunk-server中,默认64MB一个。
通过文件名和offset,master可以找到对应的chunk handle。通过handle找到chunk-server。对于nv的数据,使用write ahead log保证其可靠性。
系统启动后,chunk-severs向master注册自己,然后周期性由master向chunk-server发送心跳,用于保活及状态信息同步(piggybacked)。
这样做的好处是简化了master处理chunk-sever上下线的复杂度,master在系统中是个单点,它的实现越简单越好。
master在全局视角下存在单点故障,但实际上他也通过WAL将状态持久化并同步给其它backup节点来保障可用性。

读取很简单,client向master询问filename,及offset。master返回chunk-server地址列表。client请求chunk-server读取数据,并把filename-> chunk-servers的mapping缓存到本地,以减少master的请求次数。

上面是一个正常数据写入流程,如果写入过程某个chunk-server写失败了怎么办?
| replica A | replica B | replica C |
|---|---|---|
| B(retry success) | B(retry success) | B(retry success) |
| C | C | C |
| B | B | N/A(写入失败) |
| A | A | A |
某个secondary replica写入失败client会重试,这样导致,如A副本中:ABCB的结果,与我们预期不一致。GFS不提供数据一致性的保证。
一般有2种办法解决这个问题:
GFS没有提供回滚的机制,比如replica C写B失败时,回滚replica A/B的写入,来保证数据正确性和强一致。这样的设计可以把系统复杂度降低,试想一下如果要处理这个出错,一种方法是需要master重新选择一个新的replica节点,重新走一遍chain replica和apply mutation过程,这个类似整个流程的重试动作,使primary和master的实现都变复杂,另一种方式是使用paxos写入,性能就差多了。这里牺牲强一致性是一个trade-off,出错后复用garbage-collect的逻辑,使整个设计变的自然、简洁,chain-replication也使性能出色。
切换到backup master。可以认为master和backup master是CNAME下2条记录,backup master通过WAL同步master的操作记录。当master挂掉以后,自动切换到backup即可。切换期间GFS短暂不可用。
假设master与primary发生网络分区,master通过heartbeat检测到primary失败,等待lease过期后,选一位新的primary节点(lease过期前写不可用)。
对于旧的primary节点,若从网络分区中恢复,master检测其chunk version #,检查是否成为stale节点。若是则选一些新的节点代替stole节点,stole节点在执行完garbage-collect后重新加入集群。
secondary replica失败会导致client mutation操作失败,此时client会负责重试。多次重试失败后,返回错误。
这时该replica因为缺少了这部分mutation,会变成stale节点。对其后续处理过程如primary。
client可能是从stale replica上读数据。用户需要自行处理这些stale read,GFS不保障chunk的强一致性读。(stale节点GC需要一个过程)