存储系统如何适配 Hadoop()

近年来,Hadoop 相关的大数据框架非常成熟,应用广泛。与此同时,Hadoop 默认的存储组件 HDFS 开始逐渐暴露出自己的短板:存算一体带来的资源不匹配,NameNode 的扩展瓶颈等等。
因此,Hadoop 的存算分离成为了热门趋势。使用者开始用对象存储、分布式文件存储等存储产品,来替代 HDFS,在规避了 HDFS 的同时,又能利用好其他存储系统的优势特性。
那么,存储系统应该如何适配 Hadoop 呢?
官方指南 1、适配方案
目前,Hadoop 官方给出的存储兼容方案,主要有两个文档:
HCFS (https://cwiki.apache.org/conf...)
文档中定义了分布式文件系统适配 Hadoop 的两种方式:

All such filesystems (including HDFS) must link up to Hadoop in two ways.
The filesystem looks like a "native" filesystem, and is accessed as a local FS, perhaps with some filesystem-specific means of telling the MapReduce layer which TaskTracker is closest to the data.
The filesystem provides an implementation of the org.apache.hadoop.fs.FileSystem class (and in Hadoop v2, in implementation of the FileContext class}
即:
将文件系统作为“原生”文件系统。按照本地磁盘的方式进行访问。
【存储系统如何适配 Hadoop()】文件系统提供一个org.apache.hadoop.fs.FileSystem类,供 Hadoop 应用进行访问。
第一种方式很好理解,就是通过其他文件系统挂载到本地目录,然后按照本地目录的方式去使用。但是这种方式也存在着一定的局限——它可以完成基本的功能,但是无法针对计算场景做额外的缓存。
第二种是目前主流的方式,通过实现 FileSystem 接口,与自定义的存储系统进行交互。因为最终实现的 jar 包会部署在计算节点上,所以可以针对性地做一些优化工作,比如数据预读、支持不同的写缓存策略,或是更复杂的分布式缓存等等。
还有另外一种方式文档中没有介绍,就是通过实现 HDFS 的后端服务来进行对接。这种方式相对来说使用比较少,我们会在最后做简要介绍。
接下来,详细介绍一下 FileSystem 目前的设计方式,以及如何去实现相关的功能。
2、接口文档
Hadoop 官方在用户手册中,详细介绍了相关的接口,包括接口前置条件、完成表现、原子性要求、一致性要求等等。详细文档地址如下:
https://hadoop.apache.org/doc...
但是,在开发初期,并不建议直接看这个文档,原因是:
第一,内容非常繁琐,同名的系列函数会逐一介绍。完全看完耗时很久,而且在关注细节的过程中会失去对整体的概念;
第二,目前文中有部分列出的接口已经可以实现了,并不需要过多关注。另外它们基于某个其他抽象接口实现的功能,你只需要实现抽象接口即可;
第三,文档的内容,与实际代码接口有所差异,并且文章中多处写到,如果文档与 HDFS 有所差异,以 HDFS 为准;
基于上述原因,建议开发者直接从 Hadoop 的源码,或其他适配的存储系统的适配代码着手,开始了解相关设计。
FileSystem 的接口设计 Hadoop 调用的接口分为 1.x 的 FileSystem 和 2.x 的 FileContext。
1、Hadoop 1.x 接口 FileSystem
存储系统如何适配 Hadoop()
文章图片

FileSystem 的实现,可以分为两类:
  • 实际功能类实现
Hadoop 已经内置支持了很多的存储系统,例如本地文件系统 RawLocalFileSystem,基于 AmazonS3 的 S3AFileSystem,以及基于 FTP 的 FTPFileSystem 等等。新的存储系统做适配,也要采用这种方式来做实现。
  • 装饰器类实现
FilterFileSystem 本身并没有实现具体的行为,而是调用一个 FileSystem 对象接口,起到了装饰器的功能。它主要用于派生类实现额外的功能逻辑,而不需要关注底层的具体存储系统。
2、Hadoop 2.x 接口 FileContext 存储系统如何适配 Hadoop()
文章图片

当你看到 1.x、2.x 接口的时候,可能会想是不是需要实现两份代码?不需要担心,Hadoop 的接口设计很好地避免了这个问题。FileContext 本身并不是接口,它通过调用 AbstractFileSystem 的接口来完成功能。AbstractFileSystem 的实现分为三种:
  • DelegateToFileSystem:这个类中将所有 2.x 接口的函数使用了 1.x 的 FileSystem 做了实现。因此,我们并不需要再把 1.x 的任务重复一遍,只需要继承这个类,做一些我们期望的接口改动即可;
  • FilterFs:同 1.x 的 FilterFileSystem,起到装饰器的功能;
  • 其他:如果不希望直接用 1.x 接口来做实现,直接继承 AbstractFileSystem 来实现功能也没有问题。
存储系统的常见适配方式 了解完 Hadoop 的接口之后,还需要考虑在实现中如何与自己的存储对接,具体指的是和存储系统的架构、功能考量、开发量考量等等问题。目前,有很多存储系统已经做了支持,我们可以做一些了解,作为参考,大致可以分为以下几类:
实现方式 详细描述 优点 缺点
LocalFS + 挂载 将存储系统挂载为本地目录。 1.不需要开发,不需要部署 jar 包 1.没有扩展性,无法实现额外功能;
2.需要在所有的计算节点上,挂载存储系统。
ToPosixFileSystem + 挂载 将存储系统挂载为本地目录。实现 FileSystem 的时候,转为本地目录的操作。
虽然看起来和 LocalFS 似乎一样,但是 FileSystem 在实现的时候可以做扩展、优化。
1.不需要再实现一套新的客户端,可以复用原有的客户端系统;
2.可以做一些计算节点上的优化,例如缓存、读写策略等。
1.需要在所有的计算节点上,挂载存储系统。
ToServerFileSystem FileSystem 作为一个客户端来实现,直接与存储系统后端进行交互。
这里边又可以细分为几种:
1.与后端服务直接交互;
2.通过后端 Proxy 服务,将请求做中转;
3.将原生客户端封装为 lib,通过 JNI 调用;
4.通过 REST 方式进行 HTTP 交互。
1.可以做一些计算节点上的优化,例如缓存、读写策略等;
2.不需要在计算节点挂载存储系统;
3.减少了一层代码中转,如果有新的功能需求也不受原有客户端的限制。
1.开发量较大。这种是需要开发最多的方式之一,如果客户端已经有了比较好的封装,会相对容易一些。
HDFSServer 即实现支持 HDFS 协议的后端服务。
相对来说最繁琐的方式,需要对 HDFS 的代码有深入了解,并且需要 HDFS 后端服务可以很好地与存储系统本身做对接。
1.对 HDFS 用户最友好,可以无缝对接。 1.开发量最大,并且需要深入了解HDFS;
2.需要存储系统可以和 HDFS 能够很好的对接,否则可能会对性能等造成影响。
后续扩展 本文只对存储系统对接 Hadoop 做了简单的基础性功能介绍。如果后续想要进一步优化,可以在分布式缓存、扩展性、预读策略、输出缓存策略等方面去做额外的工作。感兴趣的还可以参考目前市面上一些好的案例,例如 Hadoop 自带的各种对象存储的实现。

    推荐阅读