datax插件开发HdfsReader支持parquet

数据仓库HIVE存储数据一般采用parquet格式,但Alibaba datax开源版不支持parquet格式,在网上查了很多资料,写的大多不完整,特此总结出完整版记录一下,供大家参考。

操作步骤

1.从gitee 拉取datax代码,对hdfsreader模块进行改造,主要改造以下几个类。

 pom里面添加parquet支持依赖

 <dependency>
            <groupId>org.apache.parquet</groupId>
            <artifactId>parquet-avro</artifactId>
            <version>1.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.parquet</groupId>
            <artifactId>parquet-common</artifactId>
            <version>1.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.parquet</groupId>
            <artifactId>parquet-protobuf</artifactId>
            <version>1.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.parquet</groupId>
            <artifactId>parquet-protobuf</artifactId>
            <version>1.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.parquet</groupId>
            <artifactId>parquet-hadoop</artifactId>
            <version>1.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.7.1</version>
        </dependency>

Constant如下

public class Constant {
    public static final String SOURCE_FILES = "sourceFiles";
    public static final String TEXT = "TEXT";
    public static final String ORC = "ORC";
    public static final String CSV = "CSV";
    public static final String SEQ = "SEQ";
    public static final String RC = "RC";
    public static final String PARQUET= "PARQUET";

}

HdfsFileType

public enum HdfsFileType {
    ORC, SEQ, RC, CSV, TEXT,PARQUET,
}

DFSUtil添加读取parquet方法,根据orc读取方法改造

 public void parquetFileStartRead(String sourceParquetFilePath, Configuration readerSliceConfig,
                                     RecordSender recordSender, TaskPluginCollector taskPluginCollector) {
        LOG.info(String.format("Start Read parquetfile [%s].", sourceParquetFilePath));
        List<ColumnEntry> column = UnstructuredStorageReaderUtil
                .getListColumnEntry(readerSliceConfig, com.alibaba.datax.plugin.unstructuredstorage.reader.Key.COLUMN);
        String nullFormat = readerSliceConfig.getString(NULL_FORMAT);
        boolean isReadAllColumns = false;
            Path parquetFilePath = new Path(sourceParquetFilePath);
            try {
                GroupReadSupport readSupport = new GroupReadSupport();
                ParquetReader.Builder<Group> reader= ParquetReader.builder(readSupport,parquetFilePath);
                ParquetReader<Group> build= reader.build();
                Group line = build.read();
                List<org.apache.parquet.schema.Type> typeList = line.getType().getFields();
                int size = typeList.size();
                List<Object> recordFields = null;
                int k=0;  //line = build.read()会忽略第一条数据,定义变量k解决
                while (k==0||((line = build.read()) != null)) {

                    k++;
                    recordFields = new ArrayList<Object>();
                    for (int i = 0; i < size; i++) {

                        //解决int96和int32问题
                        String schemaType = typeList.get(i).asPrimitiveType().getPrimitiveTypeName().name();
                        if (schemaType.toLowerCase().equalsIgnoreCase("int96")) {
                            Binary bin = line.getInt96(typeList.get(i).getName(), 0);
                            if (bin != null) {
                                Long longTime = ParquetTimestampUtils.getTimestampMillis(bin);
                                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                recordFields.add(sdf.format(longTime));
                            }
                        } else if (schemaType.equalsIgnoreCase("int32")) {
                            Integer timeDay = line.getInteger(typeList.get(i).getName(), 0);
                            Long time = timeDay * 24 * 60 * 60 * 1000L;
                            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
                            recordFields.add(sdf1.format(time));
                        } else {
                            try {
                                recordFields.add(line.getValueToString(i, 0));
                            }catch (Exception e){
                                recordFields.add("");
                            }

                        }

                    }

                    transportOneRecord(column, recordFields, recordSender,
                            taskPluginCollector, isReadAllColumns, nullFormat);
                }
                build.close();

            } catch (Exception e) {
                String message = String.format("从parquetfile文件路径[%s]中读取数据发生异常,[%s],请联系系统管理员。"
                        , sourceParquetFilePath, e);
                LOG.error(message);
                throw DataXException.asDataXException(HdfsReaderErrorCode.READ_FILE_ERROR, message);
            }

    }



    //判断文件是否是parquet
    private static boolean isParquetFile(Path file) {
        try {
            org.apache.parquet.hadoop.example.GroupReadSupport readSupport = new GroupReadSupport();
            ParquetReader.Builder<org.apache.parquet.example.data.Group> reader = ParquetReader.builder(readSupport, file);
            ParquetReader<Group> build = reader.build();
            if (build.read() != null) {
                return true;
            }
        } catch (IOException e) {

        }
        return false;
    }

package com.alibaba.datax.plugin.reader.hdfsreader;

import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import org.apache.parquet.io.api.Binary;

import java.util.concurrent.TimeUnit;


public class ParquetTimestampUtils {
    private static final int JULIAN_EPOCH_OFFSET_DAYS = 2440588;
    private static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1);
    private static final long NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1);

    private ParquetTimestampUtils() {}

    /**
     * Returns GMT timestamp from binary encoded parquet timestamp (12 bytes - julian date + time of day nanos).
     *
     * @param timestampBinary INT96 parquet timestamp
     * @return timestamp in millis, GMT timezone
     */
    public static long getTimestampMillis(Binary timestampBinary)
    {
        if (timestampBinary.length() != 12) {
            return 0;
//            throw new PrestoException(HIVE_BAD_DATA, "Parquet timestamp must be 12 bytes, actual " + timestampBinary.length());
        }
        byte[] bytes = timestampBinary.getBytes();

        // little endian encoding - need to invert byte order
        long timeOfDayNanos = Longs.fromBytes(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
        int julianDay = Ints.fromBytes(bytes[11], bytes[10], bytes[9], bytes[8]);

        return julianDayToMillis(julianDay) + (timeOfDayNanos / NANOS_PER_MILLISECOND);
    }

    private static long julianDayToMillis(int julianDay)
    {
        return (julianDay - JULIAN_EPOCH_OFFSET_DAYS) * MILLIS_IN_DAY;
    }
}


最后是HdfsReader,添加parquet

 重新打包hdfsreader,将包替换到datax引擎即可。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
DataX是一个开源的数据同步工具,由阿里巴巴集团开发和维护。它可以帮助用户高效地将不同类型、不同结构的数据从一个地方同步到另一个地方。DataX插件开发是指开发自定义的数据传输插件,以满足特定的数据同步需求。 DataX插件开发主要包括以下几个步骤: 1. 理解数据源:首先,需要了解源数据的类型和结构,比如关系型数据库、文本文件、NoSQL数据库等,以及数据的存储方式和特点。 2. 编写插件代码:根据源数据的特点,使用Java或其他编程语言编写相应的插件代码。可以参考DataX提供的现有插件代码,了解其实现原理。插件代码需要实现数据的读取、转换和写入等逻辑。 3. 配置插件参数:为了使插件能够正确运行,需要在DataX配置文件中对插件进行相应的配置。这包括指定插件的类路径、参数传递等。 4. 测试和调试:在开发插件过程中,需要进行充分的测试和调试,以确保插件能够正常工作,并满足预期的数据同步需求。可以使用DataX提供的测试工具进行测试,定位和修复代码中的问题。 5. 部署和发布:完成插件开发和调试后,需要将插件打包成可执行的Jar包,并按照DataX的部署要求进行部署和发布。 总之,DataX插件开发是根据具体的数据同步需求开发自定义的数据传输插件。通过理解数据源、编写插件代码、配置插件参数、测试和调试,最终将插件部署和发布,实现高效、可靠的数据同步。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今朝花落悲颜色

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值