HDFS前世今生之脉络(一)

图的存储如果基于Hbase, 那么实际底层依赖的其实是HDFS, 很多时候性能优化和分析, 到了Hbase这层还是发现比较慢, 就必须得看看HDFS到底读写慢在哪了, 所以也接着学习一下分布式/大数据领域HDFS这个最熟悉的陌生人吧.

第一篇主要就讲它的前世, 顺便介绍一下相关的FS概念以及整个体系的演变, (简略版)

PS: 网上随手可搜(源自官方文档)的介绍我就不多搬了, 大家可以自己简单查查.

0x00. 前言

这里的概念比较多, 先得汇总学习一下, 以免后面经常蹦出不熟悉的名词

0. 存储系统

存储, 和文件系统是整个计算机体系里非常基础核心的一环, 特别是在Unix这种一切皆文件的哲学下, 但是分布式的文件系统和经典的Linux的VFS是有一些相同和不同之处的, 包括有些概念需要梳理清楚, 再去学习才会更加的系统全面.

首先, 存储 > 文件系统(FS) , 存储常见有几个应用场景:

  1. 文件系统 (File System) : 最典型的应用包括exFAT, EXT4, XFS, GFS, NFS这种传统文件系统和文件共享服务
  2. 块存储 (Block Storage) : 最典型的例子就是作为块设备, 像磁盘阵列系统一样 (Ceph的RBD)
  3. 对象存储 (Object Storage) : 是一种结合了快K-V高速读写 + 文件存储可网络共享优点的场景 (比如S3/Swift)

这三个常见应用场景里, 今天主要就说的是第一个—-文件系统, 然后它又可以大致分为常见的三类.

1. 本地文件系统 (最常见)

文件系统简单说就是对应磁盘物理数据的一个管理系统(当然磁盘应该衍生为IO设备), 我理解它核心解答两个问题:

  • 有一个新文件, 我要把它存到磁盘上, FS是怎么存的?
  • 我要从磁盘上找到刚存的这个文件, 并读取出它的内容, FS怎么做?

这两个问题, 再抽象一下也就是我们常说的I/O (Input/Output)了. FS的最根本就是解决如何写, 如何读文件的问题 ~

这个很好理解, 那再具化一点, 这个新文件要写到磁盘上, FS首先得知道磁盘本身多大, 再得这个文件多大, 然后是该放到什么逻辑位置(比如C盘, /root/), 以及这个地址对应在磁盘上的物理位置, 还有一些其他的meta[元]信息(创建/修改时间, 剩余空间等等), 这就是FS具化常见要做的事情了.

如果一个程序自己能完全管理文件的读取和写入到磁盘, 那就取代了文件系统的功能, 同理也就可以绕开OS(操作系统)了, 最典型的例子就是用汇编直接写文件到磁盘的扇区.但是这样做需要关注许多底层细节, 并且还要自己维护这个映射表, 稍有不慎就会冲突 / 错乱, 所以在OS这种复杂的体系里, 一般都是必有对应的FS来管理磁盘(IO设备上)的数据

刚说写入文件的时候, 提到了一个很关键的元信息(meta)的概念, 不同的FS的元信息也会不同, 并且它既然是数据, 自然也是需要占磁盘空间存储的, 一般元信息所占的存储空间会单独合数据区域分开(占比很小), 所以我们可以看到在Windows上如果要改变存储介质的FS, 一般是需要经过格式化的操作的, 当然, 这里格式化是为了方便的去清理之前的FS元信息, 然后写入新的. (比如常见的把FAT32改为exFAT, 后者已经向Linux开源)

diskFormat00

所以可以看到, FS是有很多的实现的, 比如Win常见的NTFS和进化版ReFS, Linux上的ext2/3/4系列, 也有不少服务端使用XFS这种特定场景下性能更好的(比如CentOS7就默认使用), 这是传统意义上我们说的FS, 多指的是单机下OS层中的一个模块, 也可以称作磁盘文件系统, 再来看看它之后衍生出的其它同胞

2. 网络文件系统

网络FS和普通FS概念上就有很大区别, 本地FS如开始所说定义的是”文件在磁盘上如何存储“, 而网络FS定义的则是”文件在网络中如何传输“, 基于普通FS之上, 本质是解决一个文件共享的问题.

最常见的比如家庭内搭建一个私有云存储, 使用NAS设备, 然后结合NFS/CIFS网络文件系统, 前者只是Unix之间共享数据, 后者可以在Unix和Windows之间. 注意这里有些定义里把NFS/CIFS称为是一种协议而不是FS, 就是为了与传统FS区分, 但是实际上FTP/SFTP 这种才是传输协议, 你从单词的缩写也能看出, 一个后缀是FS, 一个是Protocol

3. 分布式文件系统

最后就是我们之后要说的DFS(分布式文件系统)了, 理解它的功能也比较简单, 就是把原本单台机器的存储变为多台可以一起整合, 对外界来看是一个整体服务, 也就是Google最开始用分布式服务器代替传统磁盘阵列等专用存储器的开始, 这也就划开了大数据时代的开端(2000年出头)

对比本地FS和分布式FS个, 可以这样简单理解:

  • 分布式FS : 确定某文件在哪些服务器的哪些盘上
  • 本地FS: 确定文件在磁盘的哪个具体位置, 获取原始数据

本地FS, 例如NTFS/EXT4/XFS都十分稳定, 但是它们并未针对分布式设计考量, 而传统的分布式FS如基于这种双层FS架构, 始终就像隔着一层虚拟机, 很难对磁盘性能进行特别的优化和挖掘, 就像图里的两个核心问题 —- 原生图存储, 以及分布式实现一样, 一般难以兼得, 而很多开源图采用的基于已有分布式KV存储的架构, 其实和常见的分布式FS实现思路颇为类似.

CephFS是一个相对独立于GFS体系外的著名分布式FS, 以前它也是基于这种双层FS结构, 底层要求格式化为XFS, 而近年来的新版则采用了自己专门定制的本地FS(bluestore), 从而使得CephFS也能”直接”操纵硬盘了, 并且可以针对新硬件做特定优化了, 不过虽然业界比较认可这种做法, 但是这样的架构变得复杂了许多, 并且稳定性也下降了不少. (未来趋势?)

大部分的分布式FS简单来看可以这样理解, 不过具体的实现细节差别就比较大了, 有些结构相对复杂, 自带有元信息服务器, 而有些(GlusterFS)就没有, 有些原生就提供了HA的设计和实现, 另一些可能就不会集成, 这个先简单了解一下即可.

然后以GFS作为分布式开山三驾马车之一登场, 其它常见开源分布式文件系统有 : HDFS, GlusterFS, Alluxio(内存), TFS, IPFS等等… 来看个时间线

fsTimeline00

传统业界认为:

  1. HDFS最适合作为大文件冷数据的文件系统场景
  2. Ceph是少数可以通吃所有存储场景的, 不过作为文件系统不够稳定, 一般首选是作为块存储, 其次是对象存储
  3. 这里的Colossus特别注意一下, 因为它就是GFS2(推荐)的同名, 是Google在GFS的基础上做了许多核心改动之后的没有公布の亲儿子. 哪天一旦公开哪怕是论文, 我想整个DFS界又会马上颠覆了(所以取了个晦涩的名字掩人耳目?)

补充: 还有一些特别的FS, 专门用于管理系统设备, 调内核接口的, 不与磁盘数据打交道, 只是单纯提供File API 给用户, 这里就不单独介绍了.

0x01. Linuxの文件系统 (待拆分)

A. 整体框架

HDFS基于Linux的FS之上, 所以学习它之前, 我觉得简单搞清楚Linux的FS体系也是必要的, 不然仿若云端漫步, 虚实不定.. 而且很多分布式FS里待会用到Linux原生提供的一些高级特性, 比如利用mmap()实现的 “Zero Copy(零拷贝)” . 以及各种黑科技绕过VFS等.

首先, VFS(Virtual FS)是虚拟文件系统的意思, 它是Linux Kernel和真正的文件系统之间的一层抽象接口, 而不是直接的文件系统, 举个简单的例子, 当你在命令行敲下ls命令读取当前路径下的文件, 它从用户态传到内核态到读磁盘数据的大体过程如下(参考图) :

linuxVFS00

然后细化一下, 至少有以下的步骤:

User space –> VFS抽象—> FS映射–> Block I/O层 –> I/O 调度层(可选) –> Block Driver –> Block Device ,从Linux-kernel-expl找到对应的这张图(稍加改动):

linuxBlockST01

核心的步骤对照图可以再描述为:

  1. 用户在用户态调用了read()函数, 传入”FD + 文件偏移量” 给VFS
  2. VFS判断请求的数据在内存缓冲区 (包括Page Cache)中是否存在, 存在则跳过后续步骤, 直接返回. 不存在则需要读盘 (严谨说是块设备)
  3. FS这一层相当于一个映射层(mapping layer), 记录数据的物理位置, 告诉块IO层
  4. 块IO层接到请求就可以执行读/写操作, 准备传输数据
  5. 多个IO操作, 一般又会被OS设计调度策略, 放入IO队列排序 (IO调度层)
  6. 最后, 通过磁盘驱动向磁盘控制器发送命令, 真正的执行读/写取数据.

这里面的步骤, 已经是精简过后的, 实际每一层的实现和细节都非常之多… 可参考文尾的两篇文章. (进阶推荐 LinuxのIO )

且FS模块通常被称为Linux最复杂的模块, 所以这部分先简单了解, 之后还是单独抽出来说把:)

B. IO种类

先要知道常见语言里的IO有哪些类型, 我们才好去学习后面FS的核心读写操作的各种实现, 不然看的都是流程性的东西, 总共大概4种类型:

  1. 普通IO流 (并不等于BIO)
  2. Channel传输 (文件管道方式, NIO但可能阻塞)
  3. 内存映射 (最常见的比如mmap() / sendfile()函数)
  4. 直接IO (常见调Linux底层块IO的API, 当然也可以直接用汇编绕过OS, 直接对磁盘操作)

综合了几个前辈的, 尤其是这位的全貌图之后, 我画了一些融合和精简优化, 先给了个1.0版的预览版IO与内核间的关系图出来(原图地址):

linux-io

上述图如果有什么问题(或疑问), 可以随时联系我修改调整, 还请大家多多指教~

缓存的概念:

  1. Page cache

    linux文件缓存一般称为page cache, 因为它直接缓存某个文件内容(通过内核使用page这个高级的抽象), 和FS关系更紧密, 我们一般接触/讨论的都是它, 所以有时候大家不严谨的表达可能说使用缓存就是操作page cache了

  2. Buffer cache

    一般用它缓存块设备(比如磁盘某个扇区)的数据, 所以你会看到有人说它是块缓冲器, 它不关心是否有FS这一层映射层存在, 并且FS的元数据一般也缓存在这里.

比如Cassandra中就使用了JNA库, 如何使用JNA可以先参考这篇文章, 可以简单理解为是JNI的升级库

未完待续…

0x02. 什么是HDFS

简单说了一下存储和文件系统的前世今生, 再来单独说说HDFS..

在之前的一篇图存储磁盘调研里提到了, 传统HDD的和SSD的特点, 不过那篇主要是侧重讲SSD, 这里接着衍生一下HDD, 用”我在深圳南山区, 要去北京中关村找异地的女朋友“为例子加深/回顾一下它的几个耗时点:

  1. 寻道 : 我要从深圳 -->北京 , 至少要花数小时的时间坐飞机/高铁, 并且开销巨大 (相当于磁头定位到数据所在位置, 不可避免且最为耗时)
  2. 潜伏+ 旋转延迟 : 我到了北京的中关村, 给女友发消息告知, 但女朋友说在开会, 还可能要补个妆再下来, 我只好在附近转悠20~60min一下, 然后女友发的定位在中关村XX座XX层, 我在楼里转来转去, 找具体的位置又花了好一段时间 . (可以再想想火车的启动和到站, 高速运转后要和站台对准是需要提前减速以便火车和站台准确对齐, 而不可能说加速冲就完事了:)
  3. 传输数据: 费劲千辛万苦, 终于见到了女朋友, 之后就开始数据交流/互通了吧? (相当于磁头读到扇区之后开始传输数据, 一般按1s传输200M最大来算, 4K耗时0.02ms, 耗时比很小了)

这里之所以专门举了个实际例子, 就是为了后面阐述如何优化HDD的读写做铺垫, 我们从上面可以看出, 异地恋实在是太痛苦了, 但是日子还是要继续, 于是我跟女朋友开始想想有没有好的办法优化:

  1. 减少寻道次数 : 约定一个月只跑一次, 但是之前每周一次数据互通, 现在变成一个月似乎不够用了 (结合3)
  2. 减少位置变更 : 如果我和女友的位置或者上班地点还是频繁变动, 那就会导致不断要去随机读新数据, 我们希望最好是固定下来, 减少寻路耗时
  3. 一次寻道多干些活 : 最后, 一个月只见一次面, 但是我可以连续待的时间长一些, 比如”一次待4天, 而不是每周去1天”, 那这就是把随机读改成了顺序读

上面的例子看完, 我们就可以开始想HDFS要针对海量数据的存储, 可能做的定制了, 本质就是让IO更加的连续, 并且每次IO读写较大的块(128/256M) ,然后对外提供一个看起来是一块”很大“的数据盘的服务.

简单就先说这些, 后续有必要的再补…

0x03. 如何定义FS优劣

判断一个文件系统是不是好于另一个, 目前认为主要是比较几个点: (完整单机FS对比参考Wiki)

  1. 基本特性和实现度 (权限, 元信息, 文件校验, 时间序管理等)
  2. 兼容和扩展性
  3. 读写性能
  4. 稳定性 : 作为存储系统的底层核心. 稳定性我想基本是最高优先级需要保障的, 性能再好但不稳定(丢数据)的FS我想都是没人敢用的..

未完待续…


参考资料:
  1. zhihu-存储系列入门
  2. Hadoop和HDFS简介
  3. 设计分布式文件系统的考量点
  4. 分布式系统的几篇文章-blog
  5. 从内核文件系统看IO过程
  6. 聊聊Linux的IO
  7. 文件IO操作的一些最佳实践
  8. 计算机整体IO
  9. polarDB性能大赛分享(待读)
  10. Linux Cache机制研究