公司需求上传zip文件到hdfs中,然后解压缩。百度很久没找到直接在hadoop中解压zip的方法。大多数都是使用java连接hdfs读取、解压缩、写回hdfs中,但是这样逃不开网络带宽的限制。所以就想能不能直接在hadoop环境中解压zip,想起之前使用自定义udf函数,便想试试把Java读取解压结合到udf中,然后使用hive或者impala直接在hadoop环境下执行。

准备

新建一个空的maven项目。依赖如下。(有些不一定用上了,反正全ctrl +c了)

<dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-exec</artifactId>
            <version>1.2.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>3.1.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

UDF核心代码

package com.zx;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.ql.exec.UDF;

import java.io.*;

public class Unzip extends UDF {
    public String evaluate (String path) throws IOException {

        String dir=path.split("\\.")[0]+"/";
        Configuration conf=new Configuration();
        FileSystem fs = FileSystem.get(conf);
        ZipArchiveEntry zipEntry = null;
        try(
            // 读取hdfs上文件到输入流
            FSDataInputStream fsInputStream = fs.open(new Path(path));
            ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(fsInputStream);
        ){
            // 判断是否能获取zipEntity对象
            while ((zipEntry = zipInputStream.getNextZipEntry()) != null) {
                // 获取当前解压的文件名
                String entryName = zipEntry.getName();
                // 判断解压文件是否非文件夹
                if (!zipEntry.isDirectory()) {
                    try(
                        FSDataOutputStream fsDataOut = fs.create(new Path(dir + entryName));
                    ){
                        // 在hdfs上创建指定文件
                        int byteSize = 10 * 1024 * 1024;
                        byte[] buffer = new byte[byteSize];
                        int nNumber;
                        // 并将解压的内容写入hdfs文件
                        while ((nNumber = zipInputStream.read(buffer, 0, byteSize)) != -1) {
                            fsDataOut.write(buffer, 0, nNumber);
                        }
                        fsDataOut.flush();
                    }

                }
            }
        } catch (Exception e) {
           return e.getMessage();
        } finally {

        }
        return "success";
    }
}

解释一下evaluate方法的参数是hdfs中一个zip文件路径,在该路径同级生成一个同名目录,将解压后的文件写进去。

打包上传使用

使用maven打jar包。将jar包上传到hdfs路径下,我是使用hue的webui上传后的路径为/targetFile/300Data.zip

hadoop gz压缩存储 hadoop zip_hive


上传完成后,在impala中创建函数

create function unzip(string) returns string location 'hdfs://node1/targetFile/unzip.jar' symbol='com.zx.Unzip';

函数创建成功后测试解压300Data.zip。在impala中查询

select unzip("/targetFile/300Data.zip")

hadoop gz压缩存储 hadoop zip_java_02


查询成功,在targetFile下又300Data目录和目录下解压后的txt文件,解压完成。

hadoop gz压缩存储 hadoop zip_impala_03


在项目中直接使用impala执行该语句就行,比如我使用的是springboot配置好impala数据源,直接执行。

方法二

第一种函数对于压缩包比较大的情况下会超时。导致不能全部解压。
所以可以改造一下,添加一个main方法打jar包上传到hadoop所在linux中

public static void main(String[] args) throws IOException {

        if(args==null||args.length==0)throw new RuntimeException("请输入要解压的zip文件路径");
        int count=0;
        long start=System.currentTimeMillis();
        String path=args[0];
        String dir=path.split("\\.")[0]+"/";
        Configuration conf=new Configuration();
        FileSystem fs = FileSystem.get(conf);
        ZipArchiveEntry zipEntry = null;
        try(
            // 读取hdfs上文件到输入流
            FSDataInputStream fsInputStream = fs.open(new Path(path));
            ZipArchiveInputStream zipInputStream = new ZipArchiveInputStream(fsInputStream);
        ){
            // 判断是否能获取zipEntity对象
            while ((zipEntry = zipInputStream.getNextZipEntry()) != null) {
                // 获取当前解压的文件名
                String entryName = zipEntry.getName();
                // 判断解压文件是否非文件夹
                if (!zipEntry.isDirectory()) {
                    System.out.println("正在解压第 "+(++count)+" 个文件");
                    try(
                        FSDataOutputStream fsDataOut = fs.create(new Path(dir + entryName));
                    ){
                        // 在hdfs上创建指定文件
                        int byteSize = 10 * 1024 * 1024;
                        byte[] buffer = new byte[byteSize];
                        int nNumber;
                        // 并将解压的内容写入hdfs文件
                        while ((nNumber = zipInputStream.read(buffer, 0, byteSize)) != -1) {
                            fsDataOut.write(buffer, 0, nNumber);
                        }
                        fsDataOut.flush();
                    }

                }
            }
            long t=(System.currentTimeMillis()-start)/1000;

            System.out.println("全部解压完成共 "+count+" 个文件,共用时 "+t+" 秒");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

        }
    }

我是上传到/root下通过下方命令执行

hadoop jar hadoopUnzip.jar /targetFile/da8017b3a5574c1cb0cdabbd23273e16.zip

结果如下:
[root@node1 ~]# hadoop jar hadoopUnzip.jar /targetFile/da8017b3a5574c1cb0cdabbd23273e16.zip
WARNING: Use "yarn jar" to launch YARN applications.
正在解压第 1 个文件
正在解压第 2 个文件
正在解压第 3 个文件
正在解压第 4 个文件
正在解压第 5 个文件
正在解压第 6 个文件
正在解压第 7 个文件
正在解压第 8 个文件
正在解压第 9 个文件
正在解压第 10 个文件
正在解压第 11 个文件
正在解压第 12 个文件
正在解压第 13 个文件
正在解压第 14 个文件
正在解压第 15 个文件