第二节课主要以代码的形式讲解了一下RPC以及golang并发编程,看完之后就可以完成lab1。这里直接开始第三节课。

存储

还记的分布式系统的三大基础架构:存储,计算,通信。通过前两节的MapReduce和RPC,计算和通信已经讲解过了,这一节介绍了存储以及谷歌的解决方案GFS。

设计难点

并发性问题举例: W1写1,W2写2;R1和R2准备读取数据。W1和W2并发写,在不关心谁先谁后的情况下,考虑一致性,则我们希望R1和R2都读取到1或者都读取到2,R1和R2读取的值应该一致。(可通过分布式锁等机制解决)

故障/失败问题举例:

一般为了容错性,会通过复制的方式解决。而不成熟的复制操作,会导致读者在不做修改的情况下读取到两次不同的数据。比如,我们要求所有写者写数据时,需要往S1和S2都写一份。此时W1和W2并发地分别写1和2到S1、S2,而R1和R2即使在W1和W2都完成写数操作后,再从S1或S2读数时结果可能是1也可能是2(因为没有明确的协议指出这里W1和W2的数据在S1、S2上以什么方式存储,可能1被2覆盖,反之亦然)。

GFS

每一个分布式系统都是按照它的应用场景的特点来设计的,GFS就是为MapReduce而设计的。在这里,需要的是高吞吐,高性能,且有容错。

假设有10台机器,而每一台机器的IO极限带宽为30MB/s,GFS就可以达到300MB/s的总带宽。GFS的论文open in new window

特征

  • big data set
  • fast:automatic sharding
  • global:for all apps GFS has the same view
  • FT:automatic recover

数据读取

image-20230920211527665

Master

在GFS设计中master是single system,并且为了能够更快的响应,数据存放在内存中。

数据:

  • filename <---> chunk handle

  • chunk handle <---> version + List of chunk server

  • chunk server <---> primary server + some secondaries server

    • primary server 还存放着租约事件,在这个事件内primary是有效的
  • log + checkpoint ,参见ostep-persistence中的崩溃日志一章。

其中,第一条和第二条中的version需要被安全的持久化起来,其他的可以通过和chunk server沟通获取。

读取

以下文字来源于参考笔记open in new window

  • Client向Master发请求,要求读取X文件的Y偏移量的数据

  • Master回复Client,X文件Y偏移量相关的块句柄、块服务器列表、版本号(chunk handle, list of chunk servers, version)

  • Client 缓存cache块服务器列表(list of chunk servers)

  • Client从最近的服务器请求chunk数据(reads from closest servers)

  • 被Client访问的chunk server检查version,version正确则返回数据

写入

以下文字来源于参考笔记open in new window

  • Client向Master发出请求,查询应该往哪里写入filename对应的文件。

  • Master查询filename到chunk handle映射关系的表,找到需要修改的chunk handle后,再查询chunk handle到chunk server数组映射关系的表,以list of chunk servers(primary、secondaries、version信息)作为Client请求的响应结果

  • 接下去有两种情况,已有primary和没有primary(假设这是系统刚启动后不久,还没有primary)

    有primary:继续后续流程

    无primary

    • master在chunk servers中选出一个作为primary,其余的chunk server作为secondaries。(暂时不考虑选出的细节和步骤)
    • master会增加version(每次有新的primary时,都需要考虑时进入了一个new epoch,所以需要维护新的version),然后向primary和secondaries发送新的version,并且会发给primary有效期限的租约lease。这里primary和secondaries需要将version存储到磁盘,否则重启后内存数据丢失,无法让master信服自己拥有最新version的数据(同理Master也是将version存储在磁盘中)。
    • Client发送数据到想写入的chunk servers(primary和secondaries),有趣的是,这里Client只需访问最近的secondary,而这个被访问的secondary会将数据也转发到列表中的下一个chunk server,此时数据还不会真正被chunk severs存储。(即上图中间黑色粗箭头,secondary收到数据后,马上将数据推送到其他本次需要写的chunk server)

这么做提高了Client的吞吐量,避免Client本身需要消耗大量网络接口资源往primary和多个secondaries都发送数据。

  • 数据传递完毕后,Client向primary发送一个message,表明本次为append操作。primary此时需要做几件事:

    • primary此时会检查version,如果version不匹配,那么Client的操作会被拒绝
    • primary检查lease是否还有效,如果自己的lease无效了,则不再接受任何mutation operations(因为租约无效时,外部可能已经存在一个新的primary了)
    • 如果version、lease都有效,那么primary会选择一个offset用于写入
    • primary将前面接收到的数据写入稳定存储中
  • primary发送消息到secondaries,表示需要将之前接收的数据写入指定的offset

  • secondaries写入数据到primary指定的offset中,并回应primary已完成数据写入

  • primary回应Client,你想append追加的数据已完成写入

  • 当然,存在一些情况导致数据append失败,此时primary本身写入成功,但是后续存在某些/某个secondaries写入失败,此时会向Client返回错误error。Client遇到这种错误后,通常会retry整个流程直到数据成功append,这也就是所谓的最少一次语义(do at-least-once)。

上次更新:
Contributors: YangZhang