java 从 HDFS 读取数据到本地文件

最近很烦,准确来说是有点慌了,很清楚原因是什么,同时也很清楚短期内解决不了,还是先调整心态吧。废话了。

最近在搞大数据的东东,也边学边弄,一些小细节,整理下。


场景描述

算法模型是 java 代码使用 spark-submit yarn cluster 运行的,输出结果存储在了 HDFS 上,可能因为数据结构比较复杂吧,所以没有选择将结果存储在 hive 表中。这样的话,当后期在从 HDFS 读取结果时就会遇到数据合并的问题。

比如:模型输出就是三行 json 数据。如下:

{"description":"desc-1","id":"1","name":"name-1"}
{"description":"desc-2","id":"2","name":"name-2"}
{"description":"desc-3","id":"3","name":"name-3"}

同时在代码中设定存储文件为/user/deployer/people-result ,但实际存储在 HDFS 上时的目录结构是这样的:

(base) [deployer@sh01 ~]$ hadoop fs -ls /user/deployer/people-result
Found 3 items
-rw-r--r--   3 deployer supergroup          0 2022-07-05 18:44 /user/deployer/people-result/_SUCCESS
-rw-r--r--   3 deployer supergroup         50 2022-07-05 18:44 /user/deployer/people-result/part-00000-3d314ceb-5fd4-4a13-9d16-da3c838aa13f-c000.json
-rw-r--r--   3 deployer supergroup        100 2022-07-05 18:44 /user/deployer/people-result/part-00001-3d314ceb-5fd4-4a13-9d16-da3c838aa13f-c000.json

给定的存储文件会被创建成目录,数据被分散存储在该目录下的若干个 .json 文件中。

后期读取肯定需要合并数据。若手写代码合并不仅工作量大,而且也容易出错。


解决办法

java 已经提供了读取且合并的 API: FileUtil.copyMerge(该 API 在 hadoop 3.X 中被取消了,后面有解决办法)。先上代码:

import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest()
public class DownloadFileFromHdfs {

  @Test
  public void test_download_and_merge() throws IOException {
    String src = "/user/deployer/people-result";
    String dst = "/Users/mac/Downloads/people-result.json";
    boolean result = copyMerge(src, dst);
    System.out.println(result);
  }

  private boolean copyMerge(String src, String dst)
      throws IOException {
    Configuration conf = new Configuration();
    FileSystem hdfs = FileSystem.get(conf);
    FileSystem local = FileSystem.getLocal(conf);
    final boolean result;

    try {
      Path srcPath = new Path(src);
      Path dstPath = new Path(dst);
      boolean deleteSource = false;
      String addString = null;
      result = FileUtil.copyMerge(hdfs, srcPath, local, dstPath, deleteSource, conf,
          addString);
    } finally {
      hdfs.close();
    }
    return result;
  }
}

上面的代码很简单,需要说明两点:

  1. 使用时需要分清楚是从哪里读取到哪里。在上述场景中 src 是目录,dst 是文件。

  2. Configuration 类中保存了大数据集群的配置,所以需要将 core-site.xmlhdfs-site.xml 提前存放到 classpath 目录下,等程序启动时 xml 文件中的信息会被自动读取到配置类中。
    方式一: 将上述两个 xml 文件直接放在 spring boot 项目的 resource 目录下,直接打成 jar 包;
    方式二: 将 xml 文件放在本地指定目录下,在启动 jar 包时用 -Xbootclasspath/a 指定该路径,比如:
    java -jar -Xbootclasspath/a:/Users/mac/tasks/xmls target/job-0.0.1-SNAPSHOT.jar


结果展示

(base) ➜  Downloads cat /Users/mac/Downloads/people-result.json
{"description":"desc-1","id":"1","name":"name-1"}
{"description":"desc-2","id":"2","name":"name-2"}
{"description":"desc-3","id":"3","name":"name-3"}

hadoop 3.X 版本合并下载

在 hadoop 3 中 copyMerge 方法被取消了,也没有给出同等功能的方法,不知道为什么要这么搞。
解决办法:将 copyMerge 方法粘到本地使用。 简单!粗暴!有效!

package com.demo.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;

public class FileUtils {

  public static boolean copyMerge(FileSystem srcFS, Path srcDir,
      FileSystem dstFS, Path dstFile, boolean deleteSource,
      Configuration conf, String addString) throws IOException {
    dstFile = checkDest(srcDir.getName(), dstFS, dstFile, false);

    if (!srcFS.getFileStatus(srcDir).isDirectory()) {
      return false;
    }

    OutputStream out = dstFS.create(dstFile);

    try {
      FileStatus contents[] = srcFS.listStatus(srcDir);
      Arrays.sort(contents);
      for (int i = 0; i < contents.length; i++) {
        if (contents[i].isFile()) {
          InputStream in = srcFS.open(contents[i].getPath());
          try {
            IOUtils.copyBytes(in, out, conf, false);
            if (addString != null) {
              out.write(addString.getBytes("UTF-8"));
            }
          } finally {
            in.close();
          }
        }
      }
    } finally {
      out.close();
    }

    if (deleteSource) {
      return srcFS.delete(srcDir, true);
    } else {
      return true;
    }
  }

  private static Path checkDest(String srcName, FileSystem dstFS, Path dst,
      boolean overwrite) throws IOException {
    if (dstFS.exists(dst)) {
      FileStatus sdst = dstFS.getFileStatus(dst);
      if (sdst.isDirectory()) {
        if (null == srcName) {
          throw new IOException("Target " + dst + " is a directory");
        }
        return checkDest(null, dstFS, new Path(dst, srcName), overwrite);
      } else if (!overwrite) {
        throw new IOException("Target " + dst + " already exists");
      }
    }
    return dst;
  }
}

依赖

    <dependency>
      <groupId>org.apache.sparkgroupId>
      <artifactId>spark-core_2.12artifactId>
      <version>3.3.0version>
    dependency>

你可能感兴趣的:(Spring,Boot学习笔记,大数据,java,hdfs,hadoop,读取数据,本地)