1 - 为什么要合并小文件

HDFS 擅长存储大文件

我们知道,HDFS 中,每个文件都有各自的元数据信息,如果 HDFS 中有大量的小文件,就会导致元数据爆炸,集群管理的元数据的内存压力会非常大。

所以在项目中,把小文件合并成大文件,是一种很有用也很常见的优化方法。

2 - 合并本地的小文件,上传到 HDFS

将本地的多个小文件,上传到 HDFS,可以通过 HDFS 客户端的 appendToFile 命令对小文件进行合并。

在本地准备2个小文件:

# user1.txt 内容如下:
1,tom,male,16
2,jerry,male,10

# user2.txt 内容如下:
101,jack,male,19
102,rose,female,18

合并方式:

hdfs dfs -appendToFile user1.txt user2.txt /test/upload/merged_user.txt

合并后的文件内容:

HDFS 07 - HDFS 性能调优之 合并小文件-LMLPHP

3 - 合并 HDFS 的小文件,下载到本地

可以通过 HDFS 客户端的 getmerge 命令,将很多小文件合并成一个大文件,然后下载到本地。

# 先上传小文件到 HDFS:
hdfs dfs -put user1.txt user2.txt /test/upload
# 下载,同时合并:
hdfs dfs -getmerge /test/upload/user*.txt ./merged_user.txt

下载、合并后的文件内容:

HDFS 07 - HDFS 性能调优之 合并小文件-LMLPHP

4 - 通过 Java API 实现文件合并和上传

代码如下(具体测试项目,可到 我的 GitHub 查看):

@Test
public void testMergeFile() throws Exception {
    // 获取分布式文件系统
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration(), "healchow");
    FSDataOutputStream outputStream = fileSystem.create(new Path("/test/upload/merged_by_java.txt"));
    // 获取本地文件系统
    LocalFileSystem local = FileSystem.getLocal(new Configuration());
    // 通过本地文件系统获取文件列表,这里必须指定路径
    FileStatus[] fileStatuses = local.listStatus(new Path("file:/Users/healchow/bigdata/test"));
    for (FileStatus fileStatus : fileStatuses) {
        // 创建输入流,操作完即关闭
        if (fileStatus.getPath().getName().contains("user")) {
            FSDataInputStream inputStream = local.open(fileStatus.getPath());
            IOUtils.copy(inputStream, outputStream);
            IOUtils.closeQuietly(inputStream);
        }
    }

    // 关闭输出流和文件系统
    IOUtils.closeQuietly(outputStream);
    local.close();
    fileSystem.close();
}

合并的结果,和通过命令合并的完全一致:

HDFS 07 - HDFS 性能调优之 合并小文件-LMLPHP

06-21 02:21