The Google File System

gfs为google内部的文件系统,其开源实现为hdfs,大数据领域标准的开源实现

GFS是一个存储非结构化数据的存储系统,和bigtable(列存储,存储结构化数据,关系模型,表结构)是相对应的

GFS只存储数据,不关心数据的结构和内容是什么;GFS里面只存储字节码,不关心具体的数据是什么

可能我们只是需要顺序的读取文件的数据,并不需要什么额外的操作

某些场景下,就是不需要sql去对数据进行查看,单纯的只是想要对数据进行读取

而GFS是很多组件的基础,比如说HBASE等kv数据库,它们的底层就是GFS

三种结构:列存储,结构化数据,文件系统存储非结构化数据

简介

大规模可拓展容错的分布式文件系统

大规模:TB级的文件大小

拓展:10台能够存入1TB,100台能够存入10TB,水平拓展

容错:100台机器,坏了30台机器,剩下的70台照样能够提供服务,外界无感知,系统内部的错误不会被传播出去

能够运行在廉价的服务器上(有磁盘有网络,就能够运行在上面;或者是虚拟机,只要有磁盘网络即可)

因此这种能够运行在廉价服务器上的系统,极大的降低了研发成本

任何技术的演进都需要极大的降低成本(无论时间还是金钱)研发成本,服务器的成本开销


google发现,很多时候业务的需求都是在文件的末尾追加数据(9成以上的需求),也有随机读写的情况,但很少

极少数有删除修改的需求;大部分都是顺序的将数据写到文件中

如果能够支持高性能的顺序写和顺序读的话,就可以cover掉90%的需求

因此支持在文件的尾部数据进行写入(也正是这一需求,导致了api设计上的变化)

而一般的随机写和随机读也是支持的,但不保证性能上的可靠,同时对于随机写也不能保证一致性

而该系统是多机的,所以势必遇到分布式的一致性问题(可能会读取到脏数据,被删除的数据,也可能读到多个客户端读取写入时的脏数据,反正就是会读到不符合预期的数据)

但是会保证最终的一致性,即每个数据至少会被写入到一个副本中

但GFS降低了一致性的预期




设计概述

需求分析

运行在普通的机器上,机器的失效是常态(全失败和全正常的概率都是很低的,因此至少出现一台机器失败的概率就会很高)

大文件是普遍存在的(九成九的都是大文件,十几兆的几百兆的都有;存储列表信息),小文件也是存在的(比如一些id的存储)(大文件是主导的)

大规模的顺序读写是普遍存在的,但是随机读写也是存在的

也存在多个生产者对同一个大文件进行顺序写的操作

吞吐优先于延迟:使用该系统的业务方大部分不是和在线系统相关联的,而是离线分析类型的系统,不会对计算的响应时间有所要求,而是对吞吐量有所要求:因为数据量很大,所以希望在单位时间内处理大量的数据,即把网络带宽打满

而对于延迟,对单个请求的处理速度和处理时间是没有要求的,所以不是那么的在乎延迟,反而更在会一次性能处理多少的数据,即吞吐

比如说有一百个任务,会强调在多少时间内完成所有任务,而不是完成单个任务需要多少时间

因此针对的是离线系统,而不是在线系统

更加注重一秒钟能够处理到底多少个数据,而不再会处理单个数据需要多少秒

并不是说延迟不重要了,而是认为能够把网络的吞吐量给打满,每秒处理更多的数据,这是比单个延迟更加重要的事情



接口设计

需要像linux系统一样,是可以分层组织目录的

对文件的操作,增加数据,删除数据,打开文件,关闭文件,读写文件,顺序读,尾部追加数据(弱化了对文件api的操作)

还可以获取一些文件的元数据

快照:拷贝




Master节点的操作

整个GFS分为组件,master和chunk等几个组件

其中chunk节点主要是用来存储数据的,因此很多核心逻辑都是master节点实现的

比如说master节点需要执行所有的名称空间操作,管理整个系统里所有chunk的副本

能够决定chunk的存储位置,负责新的chunk和它副本

协调各种各样的系统活动以保证Chunk被完全复制, 在所有的 Chunk服务器之间的进⾏负载均衡,回收不再使⽤的存储空间

名称空间管理和锁

类似linux下的文件目录,一个一个文件夹进行嵌套,类似数状结构;可以组成一个path

需要支持并发操作,因此需要对路径节点上读锁,对文件节点上写锁

文件目录被组织成一个多叉树

可以创建和删除文件夹,移动文件夹

用同一个map,key存放目录名,value存放子目录



副本的位置

如何规划不同副本到底应该存放在哪个机房

在磁盘利用率低、最近创建次数、放置距离三者的权衡



创建,重新复制,重新负载均衡



垃圾回收

如果一个副本写入失败了,master会继续让chunk写入副本数据

但是之前写入失败了的数据,是不会被删除掉的(因为GFS为了写入性能的提高,只进行写入追加的操作,不进行删除操作)

因此就会存在垃圾文件

另一方面,磁盘也可能出现损坏的情况,比如说寄存器的零和一发生了跳变,导致数据丢失,造成了副本的失效




容错与诊断

一方面是运行的时候保证可用性

另一方面是如果运行的时候失效了,运维的时候进行服务诊断

三个方面解释高可用:(这也是运行时的可用性)


方面一:快速恢复(如果集群不可用了,可以在短时间内恢复集群)

其实就是检查点的快速重启

一般来说很多chunkserver的状态信息都是维护在master节点上的,如果master节点挂了,就会造成系统上信息的丢失

而master都是讲数据存储到内存中的

为了进程崩溃或者内存失效,也会去写一个类似redolog的操作日志,到磁盘上,是以紧追加的形式写上去的

比如一般信息的更新,租约的更新等等的信息,都会更新到日志上

同时,不仅要把redolog写到本地磁盘,还要发送给多台其他的机器(影子机器,副本服务器)

而日志会逐渐变多,导致状态恢复的时候需要大量的时间

同时,你比如说在一年内做的数据日志就有100GB,难道还需要从头对这100GB的日志进行恢复操作吗,这是很浪费时间精力的

因此可以参考redis的RDB机制,实现一个写时复制:fork一个新的进程,将此时的数据以一种序列化的机制写入到磁盘中,得到一种快速恢复的快照

然后就可以把这个检查点之间的数据日志全部给删除了

这样的话,master节点的恢复,只需要找到最近的checkpoint节点,并执行后面对应的日志,即可快速恢复

因此又有了checkpoint机制


方面二:冗余存储(如果副本丢失了,那么可以通过多余的副本对数据进行恢复)

chunk节点的复制之前说过了

而master节点的复制,master之间存在主从关系

比如说可能存在主master会挂掉,这个时候就需要副master升为主master节点

所以副master需要同步主master节点的数据


方面三:数据完整性(能够侦擦到数据中字节级别上的错误;一般通过checksum进行判断)

读写数据的时候都会去校验一下当前数据计算得到的校验和,与checksum是否一致

大概机制:每次写入数据的时候,会将数据按照一定的大小,比如64kb或是128kb的大小,对写入的数据计算一个校验和,并存储起来

而每次读取数据的时候,就会将读取到的数据先算一个校验和,再与已有的校验和进行比较,看看是否相同,

如果不相同,就会给客户端报错

这样做的目的就是,看看在这个存储的过程中,底层的存储介质是否会损坏具体的数据内容,从而保证GFS的可靠性

另一个方面(诊断工具):

能够打印各类rpc的日志




结论与反思

GFS更像是一个技术文档

如果我们想要在工作中推进我们的工作的话,需要做的一点就是

一上来不要先看代码,而是要做数据分析,观察已有系统中是否有尚未解决的问题

通过监控指标,发现已有的问题,从数据指标中发现问题

把问题的痛点给列出来以后,再基于这些痛点

给出一些假设(比如说GFS一上来就说组件的失效是一种常态,所以要更加注重容错)

比如说我要做的事情,就是用于服务哪些场景的,说明具体的场景

说明系统的定位,以及当前改进的意义

紧接着是设计系统的接口,需要从用户的角度来分析如何使用这些接口以及使用场景

再者就是剖析系统中的每个组件,深入解决每个组件中的问题,分析已有的问题

这些事情都做好以后,就需要做数据分析,将前后的指标进行对比

最后总结与回顾




reference

https://www.bilibili.com/video/BV1rw411R7rH/


 上一篇
深入理解 CPP 之 smart pointer 深入理解 CPP 之 smart pointer
shared_ptr定义遵循共享所有权的概念,即不同的 shared_ptr 对象可以与相同的指针相关联 如果指向的资源没有任何一方需要的话,就会析构并释放资源 实现每个shared_ptr对象在会在栈上设立两个指针,分别指向堆上的两个
2022-10-26
下一篇 
深入理解 CPP 之 type conversion 深入理解 CPP 之 type conversion
backround基类对象转换为派生类对象,是不安全的 派生类是包含于基类的,即基类的“体积”是小于派生类的 如果一个指向基类的指针强转为指向派生类的指针(该指针始终指向基类,只是指针类型变了),再通过该指针调用派生类的特有方法或者数据
2022-10-23
  目录