libhdfs 使用指导和性能测试结果

概述

HDFS 使用 Java 编写而成,默认提供 Java API,但有时候业务会需要 C API,在这种情况下,可以使用 libhdfs,libhdfs 随 Hadoop 一起发行,由 Hadoop 官方社区进行维护,其性能、稳定性都比较好,这篇文章介绍了 Hadoop libhdfs 的使用方法和性能测试结果。

libhdfs 使用步骤

  1. 在业务环境上安装好 Hadoop,注意以下几点:

    1. core-site.xml、hdfs-site.xml 等配置文件中,相关的必要配置项(特别是 HA、认证相关的配置项)需要先配置好。
    2. 最终请务必确保 HDFS 客户端(即 bin/hdfs dfs 系列命令)能正常工作。
  2. 配置通用环境变量(假设 hadoop 安装到了 /root/hadoop-2.7.2/)

    export HADOOP_HOME=/root/hadoop-2.7.2
    export CLASSPATH=$($HADOOP_HOME/bin/hadoop classpath --glob):$CLASSPATH
    export LD_LIBRARY_PATH=$HADOOP_HOME/lib/native:$LD_LIBRARY_PATH
    
  3. 配置业务相关环境变量
    LIBHDFS_OPTS 这个环境变量是传给 libhdfs JVM 的参数,可以通过它配置 JVM参数、HDFS Java Client 日志参数等,根据业务场景的不同,可以有不同的选择,举两个例子:

    1. 配置 Xmx 为 512M,配置 HDFS Java Client 的日志级别为 INFO,日志 appender 为 console(这是 log4j 的一个内部 appender,日志直接输出到 stderr):

       export LIBHDFS_OPTS="-Xmx512m -Dhadoop.root.logger=INFO,console"
      
    2. 配置 JVM Xmx 为 512M,配置 HDFS Java Client 的日志级别为 INFO,日志 appender 为 DRFA(这是 hadoop 自带的一个 log4j appender,按照天为单位,每天生成一个日志文件),日志目录为 /tmp,日志文件为 log.txt(生产环境下,只需要按需调整日志目录和日志文件的配置即可):

      export LIBHDFS_OPTS="-Xmx512m -Dhadoop.root.logger=INFO,DRFA -Dhadoop.log.dir=/tmp -Dhadoop.log.file=log.txt"
      
  4. 编译 .c 源文件并运行

    gcc sample.c -I $HADOOP_HOME/include -L $HADOOP_HOME/lib/native -lhdfs -o sample
    ./sample
    

libhdfs 内存泄露风险

libhdfs 的一些 API 会在内部分配内存,并作为指针返回给上层业务,业务使用完这些指针后,必须手动 free,否则会造成内存泄露,这些关键点在各个 API 的使用注释中都写得很清楚(查看 libhdfs 自带的头文件 hdfs.h 即可获取详细信息),请务必注意。

这样的 API 主要有下面这几组:

  1. 获取配置项值的一组 API:
int hdfsConfGetStr(const char *key, char **val);
void hdfsConfStrFree(char *val);
  1. 获取文件信息、list 目录的一组 API:
hdfsFileInfo *hdfsListDirectory(hdfsFS fs, const char* path, int *numEntries);
hdfsFileInfo *hdfsGetPathInfo(hdfsFS fs, const char* path);
void hdfsFreeFileInfo(hdfsFileInfo *hdfsFileInfo, int numEntries);
  1. 获取文件 block 位置的一组 API:
char*** hdfsGetHosts(hdfsFS fs, const char* path, tOffset start, tOffset length);
void hdfsFreeHosts(char ***blockHosts);
  1. 创建 zero-copy 选项的一组 API:
struct hadoopRzOptions *hadoopRzOptionsAlloc(void)
void hadoopRzOptionsFree(struct hadoopRzOptions *opts);

libhdfs 性能

问题来源

libhdfs 对外提供 C 接口,具体实现是通过 JNI 的方式调用 HDFS 的 Java 接口,这样的话在读写过程中就会涉及内存拷贝,有以下两种情况:

  1. 写过程中,需要将用户 C 代码中的 buffer 数组内容,拷贝到 JVM 中,然后调用 HDFS Java 接口执行写入。
  2. 读过程中,需要先调用 HDFS Java 接口执行读取,然后将 JVM 中的数组内容,拷贝到用户 C 代码的 buffer 数组中。

内存拷贝可能会对读写性能产生一定的影响,具体影响多少需要进行测试。

性能测试

测试环境

使用现网一台空闲机器进行测试,使用该机器搭建单节点的 HDFS 集群,并在本机器上进行读写,这样就排除了网络因素的影响。具体配置如下:

项目 配置
CPU 16核 X64 Xeon 2.10GHz
RAM 64GB
磁盘 7.3TB HDD * 12块

测试结果

  1. 两个前提

    1. 无论是 Java 测试代码,还是 libhdfs 测试代码,内部都使用 4K 的读写 buffer,这也是一个典型的 buffer 大小。
    2. 每次执行测试项目前,都需要执行 sync && echo 3 > /proc/sys/vm/drop_caches 释放文件系统缓存,保证多次测试的独立性。
  2. 写测试

文件大小 Java 客户端耗时 libhdfs 客户端耗时 libhdfs 相比 Java
2GB 4.403s 4.269s +3.12%
5GB 7.858s 7.599 +3.41
10GB 13.099s 13.371s -2.08
50GB 58.403s 59.597s -2.04%
  1. 读测试
文件大小 Java 客户端耗时 libhdfs 客户端耗时 libhdfs 相比 Java
2GB 11.006s 10.9s +0.96%
5GB 25.001s 24.331s +2.68%
10GB 46.691 46.385 +0.66%
50GB 3m54.302s 3m54.122s +0.08%
  1. 结论

可以看到,Java 客户端和 libhdfs 客户端的性能非常接近,基本可以认为相同。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容