Swift 通过 HTTP 协议对外提供存储服务,有一个 REST 风格的 API。Swift 本身是用 Python 语言实现的,但是也提供了多种编程语言的客户端 API,例如:Python、Java、PHP、C#、Ruby 等。这些客户端 API 都通过发起 HTTP 请求和接收 HTTP 响应来与 Swift 集群的代理节点进行交互,Swift 客户端 API 在 REST API 之上提供了更高层次的对容器和对象的操作,使得程序员编写访问 Swift 的程序变得更为方便。
Swift 的 Java 客户端 API 名叫 java-cloudfiles,也是一个开源项目。其中的 FilesClient 类提供了对 Swift 对象存储的各种操作,包括:登录 Swift、创建和删除 Account、容器、对象,获得 Account、容器、对象的元数据,以及读写对象的方法。其他相关的类包括:FilesContainer、FilesObject、FilesContainerInfo、FilesObjectMetaData 等,它们分别代表 Swift 中的容器和对象以及对应的元数据,如容器包含的对象个数,对象的大小、修改时间等。版本号为 1.8 的 java-cloudfiles 能够和开源版本的 Swift 兼容。Filesclient 类中主要的方法和含义如表 2 所示。
表 2. FilesClient 类中的主要方法和含义
综上所述,Hadoop FileSystem API 能够接受新的文件系统实现的机制,以及能够用 Java 语言编写应用程序与 Swift 进行交互操作,这两点使得扩展 Hadoop 抽象文件系统是可行的。
实现的操作包括:打开文件并返回输入流,创建文件并返回输出流,删除路径,判断路径是否存在,获得路径的元数据,获得文件系统的 URI,获得工作目录,创建目录等等。目录对应 Swift 中的容器,文件对应 Swift 中的对象。在实现的过程中,有几个问题需要进行特殊处理。
首先,由于在 Swift 对象存储中,名称空间是扁平的,没有目录层次结构,所以在路径上需要进行特殊处理,具体的做法是允许文件名称包含斜杠(/)。在一般的 POSIX 兼容的文件系统中,斜杠不能作为文件名的一部分,属于非法字符,而在 Swift 中是允许的。通过这种方式,可以实现虚拟的目录层次结构。此时,根路径作为容器的名称,根目录之后的整个路径都作为对象的名称。
编译源代码并打包成 JAR 文件,再将 JAR 文件及其依赖的类库部署到 Hadoop 集群中所有节点的$HADOOP_PREFIX/share/hadoop/lib 目录中。使用 RPM 文件安装的 Hadoop 的类库默认目录是/usr/share/hadoop/lib。这就像将插件安装到 Hadoop 中一样,没有对原有软件进行修改。
修改 Hadoop 集群中所有节点的配置文件 core-site.xml,使文件系统的 URI 指向 Swift 的代理节点,并指定 Swift 中的某个 Account、用户名和密码。这些属性会被 Swift 适配器读取。在 Swift 集群中部署多台代理节点,还可以使用专门的负载均衡器(Load Balancer)或轮转 DNS(Round-robin DNS)指向这些代理节点,并在 core-site.xml 中使文件系统的 URI 指向负载均衡器或轮转 DNS。配置文件 core-site.xml 的属性如表 5 所示。
表 5. 配置文件 core-site.xml 的属性
拓扑结构
Hadoop 集群中部署了 1 台 JobTracker 节点,以及多台运行 TaskTracker 的 slave 节点,所有节点均加入了 Swift 适配器 JAR 文件及其依赖的类库。Swift 集群中部署了多个 Proxy 节点和 Storage 节点,并且部署了 1 台轮转 DNS 服务器,它指向这些 Swift 集群中的代理节点。整个扩展系统的拓扑结构如图 4 所示。
图 4. 扩展系统拓扑结构图
流程
在 Swift 适配器中,以初始化文件系统实例、打开文件并读取数据、以及创建文件并写入数据的操作为例,分别叙述它们的流程,并使用 UML 时序图展示出来。
打开文件并读取数据的详细流程如下:在打开文件的时候,客户程序调用 SwiftAdapter 类的 open 方法,SwiftAdapter 对象首先初始化 Swift 输入流类 SwiftInputStream 的实例,然后 SwiftInputStream 对象会调用 FilesClient 对象的 getObject 方法向 Swift 集群中的代理服务器发起 HTTP 请求获取 Swift 中的对象,把数据存入 SwiftByteArrayInputStream 对象内部的字节数组缓冲中,之后客户端程序调用 SwiftInputStream 对象的 read 方法读取缓冲存储中的字节,读取数据的操作完成之后再调用 close 方法关闭 Swift 输入流。打开文件并读取数据的 UML 时序图如图 6 所示。
图 6. 打开文件并读取数据的 UML 时序图
创建文件并写入数据的详细流程如下:在创建文件的时候,客户程序调用 SwiftAdapter 类的 create 方法,SwiftAdapter 对象首先初始化 Swift 输出流类 SwiftOutputStream 的实例,然后客户程序调用 SwiftOutputStream 对象的 write 方法把数据写入到它内部的字节数组缓冲中,直到调用它的 flush 方法或 close 方法,SwiftOutputStream 对象才会调用 FilesClient 对象的 storeObject 方法,向 Swift 集群中的代理服务器发起 HTTP 请求将缓冲存储中的字节写入 Swift 中的对象。创建文件并写入数据的 UML 时序图如图 7 所示。
图 7. 创建文件并写入数据的 UML 时序图
未来的工作
通过 Swift 适配器,将高可用的 Swift 对象存储作为 Hadoop 的底层存储系统,使得 Hadoop 在存储层面具有了高可用性。把 Swift 适配器部署到已有的 Hadoop 集群中是简单快捷的。原本用来分析存储在 HDFS 中的数据的 MapReduce 应用程序,也无需修改即可分析存储在 Swift 中的数据。
本文分析了 Hadoop FileSystem API 和 Swift Java client API,以及 Hadoop 与 OpenStack Swift 整合的可行性,介绍了 Swift 适配器的设计和实现细节,最终将 OpenStack Swift 对象存储作为 Hadoop 的底层存储,使得它们能够协同工作,为 Hadoop 的存储层增加了对 OpenStack Swift 的支持。