本节书摘来异步社区《Hadoop MapReduce实战手册》一书中的第2章,第2.9节,作者: 【美】Srinath Perera , Thilina Gunarathne 译者: 杨卓荦 责编: 杨海玲,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.9 使用HDFS的C API(libhdfs)
Hadoop MapReduce实战手册
libhdfs是一个原生共享库,提供了一套C API,允许非Java程序与HDFS进行交互。libhdfs使用JNI通过Java与HDFS进行交互。
准备工作
目前的Hadoop发行版中包含了为32位和64位Linux操作系统预编译的libhdfs库。如果你的操作系统与预编译库不兼容,则可能需要下载Hadoop的标准发行版并从源代码编译libhdfs库。有关编译libhdfs库的信息请参见2.10节。
操作步骤
下列步骤显示了如何在HDFS环境下使用HDFS的C API执行操作。
- 下面的示例程序会在HDFS中创建一个新文件,向新建文件中写入一些文本信息,并从HDFS读回该文件。用HDFS集群中NameNode的相关值替换NAMENODE_HOSTNAME和端口变量。该hdfs_cpp_demo.c源文件中的源代码包位于该文件夹的HDFS_C_API目录中。
#include "hdfs.h"
int main(intargc, char **argv) {
hdfsFSfs =hdfsConnect( "NAMENODE_HOSTNAME,PORT");
if (!fs) {
fprintf(stderr, "Cannot connect to HDFS.\n");
exit(-1);
}
char* fileName = "demo_c.txt";
char* message = "Welcome to HDFS C API!!!";
int size = strlen(message);
int exists = hdfsExists(fs, fileName);
if (exists > -1) {
fprintf(stdout, "File %s exists!\n", fileName);
}else{
// Create and open file for writing
hdfsFileoutFile = hdfsOpenFile(fs, fileName, O_WRONLY|O_CREAT, 0, 0, 0);
if (!outFile) {
fprintf(stderr, "Failed to open %s for writing!\n", fileName);
exit(-2);
}
// write to file
hdfsWrite(fs, outFile, (void*)message, size);
hdfsCloseFile(fs, outFile);
}
// Open file for reading
hdfsFileinFile = hdfsOpenFile(fs, fileName, O_RDONLY, 0, 0, 0);
if (!inFile) {
fprintf(stderr, "Failed to open %s for reading!\n", fileName);
exit(-2);
}
char* data = malloc(sizeof(char) * size);
// Read from file.
tSizereadSize = hdfsRead(fs, inFile, (void*)data, size);
fprintf(stdout, "%s\n", data);
free(data);
hdfsCloseFile(fs, inFile);
hdfsDisconnect(fs);
return 0;
}
- 通过使用如下gcc命令编译上面的程序。当编译时,需要链接libhdfs和JVM库。因此,还必须包括Java安装路径的JNI头文件。一个示例编译命令如下所示。将ARCH和架构依赖的路径替换成当前系统的路径。
>gcchdfs_cpp_demo.c \
-I $HADOOP_HOME/src/c++/libhdfs \
-I $JAVA_HOME/include \
-I $JAVA_HOME/include/linux/ \
-L $HADOOP_HOME/c++/ARCH/lib/ \
-L $JAVA_HOME/jre/lib/ARCH/server\
-l hdfs -ljvm -o hdfs_cpp_demo
- 重置环境变量CLASSPATH,将其指向Hadoop的依赖环境。一种保险的方法是将$HADOOP_HOME和$HADOOP_HOME/lib中的所有jar文件全部包括在环境变量中。
export CLASSPATH=$HADOOP_HOME/hadoop-core-xx.jar:...
Ant构建脚本生成类路径
在2.8节步骤2中给出的build文件的末尾,添加下面的Anttarget。本章源码包的HDFS_C_API文件夹中提供了修改后的build.xml脚本。
<target name="print-cp">
<property name="classpath"`
refid="hadoop-classpath"/>`
<echo message="classpath= ${classpath}"/>`
</target>`
用ant print-cp执行Ant构建,之后会生成一个包含$HADOOP_HOME和$HADOOP_HOME/lib中的所有jar文件名的字符串。复制这个字符串导出到CLASSPATH环境变量中。
- 执行程序。
>LD_LIBRARY_PATH=$HADOOP_HOME/c++/ARCH/lib:$JAVA_HOME/jre/lib/ARCH/
server./hdfs_cpp_demo
Welcome to HDFS C API!!!
工作原理
首先,我们通过hdfsConnect命令输入NameNode的主机名(或IP地址)和端口来连接到一个HDFS集群。hdfsConnectAsUser命令可以作为一个特定的用户连接到一个HDFS集群。
hdfsFSfs =hdfsConnect("NAMENODE_HOSTNAME",PORT);
使用hdfsOpenFile命令新建文件,并获得新新建文件的句柄。O_WRONLY | O_CREAT标志表示新建一个文件或重写现有文件,并用只写模式打开它。其他支持的标志还有O_RDONLY和O_APPEND。该hdfsOpenFile命令的第四、第五和第六个参数分别表示进行读/写操作的缓冲区大小、块的冗余因子和新创建文件的块大小。如果想使用这三个参数的默认值,可以将它们指定为0。
hdfsFileoutFile = hdfsOpenFile(fs, fileName,flags, 0, 0, 0);
该hdfsWrite指令将提供的数据写入到outFile句柄指定的文件中。数据大小需要以字节数为单位指定。
hdfsWrite(fs, outFile, (void*)message, size);
hdfsRead命令从inFile指定的文件中读取数据。需要提供以字节为单位的缓冲区大小作为第四个参数。hdfsRead命令返回文件读取的实际字节数,该数量可能小于缓冲区大小。如果想要确保从文件中读取一定的字节数,最好是在循环内使用hdfsRead命令,直到读完了指定的字节数为止。
char* data = malloc(sizeof(char) * size);
tSizereadSize = hdfsRead(fs, inFile, (void*)data, size);
更多参考
HDFS的C API(libhdfs)支持的文件系统操作比前面示例中用到的多。请参见$HADOOP_HOME/src/
c++/libhdfs/hdfs.h头文件获得更多有关的详细信息。
使用HDFS的配置文件配置
还可以使用HDFS的配置文件来告诉libhdfsNameNode的主机名和端口号,而不是通过hdfs-
Connect命令以参数的方式传入。
- 在hdfsConnect命令中,将NameNode的主机名和端口号改为"default"和0。(将主机名设置为NULL将使libhdfs使用本地文件系统)。
hdfsFSfs = hdfsConnect("default",0);
- 将HDFS安装的conf目录添加到CLASSPATH环境变量中。
export CLASSPATH=$HADOOP_HOME/hadoop-core-xx.jar:...:$HADOOP_HOME/conf