MIT6.824 lec3 GFS
2022.5.15
3.1 分布式存储系统的难点(Why Hard)
- 824主要关注大型存储,因为简单的存储接口往往极其通用,构建分布式系统大多是关于如何设计存储系统。我们需要关注如何为大型分布式存储系统设计一个优秀的接口,以及如何设计存储系统的内部结构,这其中涉及到并行性能,容错,复制和一致性
- 人们涉及大型分布式系统或大型存储系统的出发点是,想要获取巨大的性能加成,进而利用数百台计算机的资源同时完成大量工作,性能问题是最初诉求。一个想法是分片,也就是将数据分割到大量的服务器,这样就可以并行的从多台服务器读取数据。
- 由于分片涉及到上千台机器,所以经常会出现故障,所以需要一个自动化容错系统,复制(replication)是实现容错的一个方法。
- 为了容错,需要设置多个副本,那么副本可能会存在不一致(inconsistency),为了处理不一致,需要不同服务器之间通过网络额外的交互,这样会导致低性能。高性能与一致性之间存在矛盾。
3.2 错误的设计
- 在客户端或应用程序看来,强一致性模型表现得就像是一台服务器,一份数据,系统只做一件事。对于存储服务器,它上面会有一块磁盘,执行一个写请求或许意味着向磁盘写入一个数据或者对数据做一次自增。如果是一次修改操作,并且我们有一个key-value为索引的数据表单,那么我们会修改这个表单,如果是一次读操作,我们只需要将之前写入的数据,从表单中取出即可即使是对一个无副本的服务器做两次写请求,仍然存在先后问题的模糊场景。
- 一个简单的多副本服务器设计,有两台服务器,每台服务器的磁盘都存在数据的拷贝,以太服务器故障了,可以去读另一台服务器,那么每一个写请求都必须写到两个服务器之上,读请求只需要在一台服务器上执行,否则就没有容错性了。假设客户端C1和C2都想执行写请求,其中一个要写X为1,另一个写X为2。C1会将写X为1的请求发送个两个服务器,因为我们想要更新两台服务器上的数据。C2也会将写X为2的请求发送给两个服务器。那么没有任何事情来保证服务器以相同的顺序处理这2个请求,如果S1先1后2,S2先2后1,则会出现不一致的情况那么之后两个客户端去读取,数据可能会不一致。可能的解决方法是要客户端都去读S1,但是当S1故障之后,客户端读的数据将从2变成1
- 当然可以通过通信解决这些问题,但是复杂度也会提升
3.3 GFS的设计目标
- GFS的目标是构建一个大型的,快速的文件系统,并且这个文件系统是全局有效的。一种构建大型存储系统的方法是针对某个特定的应用程序构建特定的裁剪的存储系统,但是如果另一个应用程序也想要一个大型存储系统,那么又需要重新构建一个存储系统。如果有一个全局通用的存储系统,那就意味着如果我存储了大量从互联网抓取的数据,你也可以通过申请权限来查看这些数据,因为我们使用了同一个存储系统,这样任何Google内部的人员可以根据名字读取这个GFS中被共享的内容
- 为了获取大容量和高速的特性,每个包含了数据的文件会被GFS自动分割存放在多个服务器之上,这样读写操作就很快。因为可以并行从多个服务器上读取数据,进而获得更高的聚合吞吐量。将文件分割还可以在存储系统中保存比单个磁盘还要大的文件
- 自动修复,在数百台服务器上构建存储系统
- GFS被设计在一个数据中心运行,所以这里没有将副本保存在世界各地,单个GFS只存在于单个数据中心的单个机房中,虽然着不是设计的目标。理论上来说,数据的多个副本一个彼此之间隔得远一些,但是实现起来困难
- GFS是内部使用的系统
- GFS在各个方面对于大型的顺序文件读写做了限制。在存储系统中有些完全不同的领域,这个领域只对小份数据进行优化。比如一个银行系统就需要一个能够读写100字节的数据库,因为100字节就可以表示人们的银行账户,但是GFS需要处理TB级别的数据,GFS只支持顺序处理,不支持随机访问,有点像批处理,GFS并没有花费过多精力来降低延迟,它只关注巨大的吞吐量
- GFS具有弱一致性,它表明存储系统具有弱一致性也是可以的,这样保证了良好的性能
- GFS的数据量之庞大,模糊了正确性的判断,应用程序应当对数据做校验,并明确标记数据的边界,这样应用程序在GFS返回不正确数据时可以恢复
3.4 GFS Master节点
- 假设我们有上百个客户端和一个Master节点,尽管实际中可以拿多态机器作为Master节点,但是GFS中Master是Active-Standby模式,所以只有一个Master节点在工作。Master节点保存了文件名和存储位置对应关系。除此之外,还有大量的Chunk服务器,可能会有上百个,每个Chunk服务器都有1-2快磁盘
- Master节点用来管理文件和Chunk的信息,而Chunk服务器用来存储实际的数据。这是GFS设计中比较好的一面,它将这两类数据管理的问题几乎完全隔离开了,这样两个问题可以使用独立设计来解决。
- Master节点知道每一个文件对应的所有Chunk的ID,这些Chunk每个是64MB大小,它们共同构成了一个文件。
- 如果我有一个1GB的文件,那么Master节点就知道文件的第一个Chunk存储在哪里,第二个Chunk存储在哪里,当我想读取这个文件的任意部分,我需要向Master节点查询对应的Chunk在哪个服务器上,之后我可以直接从Chunk服务器读取对应的Chunk数据
- 为了处理故障与实现一致性,Master节点保存了一些数据内容:
- 第一个表单是文件名到Chunk ID或者Chunk Handle数组的对应,这个表单告诉你,文件对应了哪些Chunk。这个数据要保存在磁盘上,所以标记为NV(non-volatile)
- 第二个表单记录了Chunk ID到Chunk数据的对应关系,这些数据包括:
- 每个Chunk存储在哪些服务器上,所以这部分是Chunk服务器的列表。这个数据不用保存在磁盘上,因为Master节点重启之后可以和所有的Chunk服务器通信,并查询每个Chunk服务器存储了哪些Chunk
- 每个Chunk当前的版本号,所以Master节点必须记录每个Chunk对应的版本号。版本号,我认为其需要写入磁盘。
- 所有对于Chunk的写操作都必须在主Chunk(Primary Chunk)上顺序处理,主Chunk是Chunk的多个副本之一。所以,Masrter节点必须记住哪个Chunk服务器持有主Chunk。Master节点重启之后会忘记谁是主Chunk,只需要等待60秒租约到期,那么它知道对于这个Chunk来说没有主Chunk,所以这时候可以安全的指定一个新的主Chunk,所以标记为V。
- 并且,主Chunk只能在特定的租约时间内担任主Chunk,所以Master节点要记住主Chunk的租约过期时间,不用写入磁盘。
- 以上数据都存储在内存中,如果Master宕机了,数据就丢失了,为了能使Master重启而不丢失数据,Master节点会同时将数据存储在磁盘上,所以Master节点读数据只会从内存读,但是写数据的时候,至少一部分数据会写入到磁盘中,更具体来说,Master会从磁盘上存储log,每次有数据变更时,Master会在磁盘的log中追加一条记录,并生成CheckPoint
- 任何时候,如果文件扩展到达了一个新的64MB,需要新增一个Chunk或者由于指定了新的主Chunk而导致版本号更新了(版本号更新是指什么鸭?),Master节点需要向磁盘中的Log追加一条记录,来说明刚刚向这个文件添加了一个新的Chunk或者是修改了Chunk的版本号。由于写磁盘慢,所以Master节点需要经可能少的写入数据到磁盘。
- 在磁盘中写log而不是数据库,数据库本质上是某种B数或hash table,那么写B树需要在磁盘上随机访问,而写Log是顺序写入,会快很多。
- 并且log中存在checkpoint,所以Master节点恢复从最近的checkpoint开始,逐条恢复
3.5 GFS 读文件
- 对于读请求,GFS客户端或应用程序会将一个文件名与文件中的读取偏移量发送给Master节点,那么Master节点会从file表单中查询文件名,通过偏移量除以64MB就可以得到对应的Chunk ID,之后Master节点再从Chunk表单中找到存有Chunk的服务器表单,并将列表返回给客户端
- 客户端得到Chunk服务器位置后,可以去从最近的服务器读取数据,并且会缓存Chunk ID和服务器的对应关系
- 客户端之后就和Chunk服务器通信,Chunk服务器将每个Chunk存储为独立的Linux文件
- 对于GFS客户端一次请求的数据量超过了64MB或者跨越了Chunk的边界的情况,GFS客户端每次请求会依赖于一个GFS的库,这个库会将这个请求划分为两个给Master节点,也就是GFS库和Master节点会共同协商这些信息转化为一个Chunk
3.6 GFS写文件
- 写文件也会调用GFS的库。可能存在多个客户端同时写一个文件的情况,那么多个客户端写一个同一个文件,一个个客户端没法知道其他客户端写了多少,也就不知道偏移量
- 并且,对于读文件而言,可以从任何最新的Chunk副本读,但是写文件,必须要写入到Chunk的主副本(Primary Chunk),但是存在某个时间点,Master不一定指定了Chunk的主副本,所以,写文件需要考虑Chunk的主副本不存在的情况,Master节点需要弄清楚再追加文件时,客户端需要与哪个Chunk服务器通信Master的做法是,将所有存储了最新Chunk的服务器集合完成,然后挑选一个为Primary,其他作为Secondary,这个时候Master会增加这个Chunk的版本号
- Chunk的版本号用来标识最新的副本,Master节点会记录Chunk的最新版本号,由于需要通过Chunk的版本号来区分副本是否是最新的,所以Chunk的版本号需要保存在磁盘中同时,一定要记录最新版本号,而不是最大版本号
- 大量的客户端会去向Primary发起一些并发的写请求,并且Primary需要保证所有的Secondary都写入成功了,如果没有,就会像客户端返回写入失败,那么客户端需要重新月Master交互
- 对于写文件的失败,Primary和Secondary服务器并不会去做任何的恢复
3.8 GFS的一致性
- 弱一致性,Master节点只有一台机器,并且它发生故障,需要人工切换
参考
文档
视频