Datax插件二次开发之HdfsWriter支持parquet

Date: December 24, 2021

1. 背景

目前,公司的OLAP和AD-HOC组件主要使用impala,而当前我们的impala版本支持parquet\textfile格式,却不支持ORC格式,因此会有同步数据时,进行parquet格式写入的需求。

在网上查了下资料,只找到一个支持parquet的hdfswriter插件,但是有网友乐(tian)于(bu)分(zhi)享(chi)的贴了一个版本,没有源码不说,下载下来还各种坑(只支持单表,不支持分区表),实则是明里埋坑暗里收钱,让人无语。

因此,决定自己开发个插件,开源出来,供大家一起使用。

2. 操作步骤

2.1 代码开发

通过拉取datax源码,对hdfswriter模块进行修改,主要修改HdfsHelper.java和HdfsWriter.java 两个类文件,模仿模块中orc的代码块,结合parquet API 进行开发,主要是在HdfsHelper.java中添加parquetFileStartWrite 方法,具体代码如下:

/**
     * 写parquetfile类型文件
     * @param lineReceiver
     * @param config
     * @param fileName
     * @param taskPluginCollector
     */
    public void parquetFileStartWrite(RecordReceiver lineReceiver, Configuration config, String fileName,
                                      TaskPluginCollector taskPluginCollector){
        List<Configuration>  columns = config.getListConfiguration(Key.COLUMN);
        String compress = config.getString(Key.COMPRESS, null);
        List<String> columnNames = getColumnNames(columns);
        List<ObjectInspector> columnTypeInspectors = getColumnTypeInspectors(columns);
        StructObjectInspector inspector = (StructObjectInspector)ObjectInspectorFactory
                .getStandardStructObjectInspector(columnNames, columnTypeInspectors);

        ParquetHiveSerDe parquetHiveSerDe = new ParquetHiveSerDe ();

        MapredParquetOutputFormat outFormat = new MapredParquetOutputFormat();
        if(!"NONE".equalsIgnoreCase(compress) && null != compress ) {
            Class<? extends CompressionCodec> codecClass = getCompressCodec(compress);
            if (null != codecClass) {
                outFormat.setOutputCompressorClass(conf, codecClass);
            }
        }
        try {
            Properties colProp= new Properties();
            colProp.setProperty("columns",String.join(",",columnNames));
            List<String> colTypes = new ArrayList<>();
            columns.forEach(col ->colTypes.add(col.getString(Key.TYPE)));
            colProp.setProperty("columns.types",String.join(",",colTypes));
            RecordWriter writer = (RecordWriter) outFormat.getHiveRecordWriter(conf,new Path(fileName), ObjectWritable.class,true,colProp,Reporter.NULL);
            Record record = null;
            while ((record = lineReceiver.getFromReader()) != null) {
                MutablePair<List<Object>, Boolean> transportResult =  transportOneRecord(record,columns,taskPluginCollector);
                if (!transportResult.getRight()) {
                    writer.write(null, parquetHiveSerDe.serialize(transportResult.getLeft(), inspector));
                }
            }
            writer.close(Reporter.NULL);
        } catch (Exception e) {
            String message = String.format("写文件文件[%s]时发生IO异常,请检查您的网络是否正常!", fileName);
            LOG.error(message);
            Path path = new Path(fileName);
            deleteDir(path.getParent());
            throw DataXException.asDataXException(HdfsWriterErrorCode.Write_FILE_IO_ERROR, e);
        }
    }

2.2 相关说明

虽然在hdfswriter模块中加入 parquet 代码块中后,能够在hdfs中进行parquet格式写入。但是测试的时候,却发现原来支持的ORC格式写入却发生异常了,几经debug,也没发现问题出在哪里,因此怀疑问题出现在包的版本上。

由于DATAX的hdfswriter中hive和hadoop版本为:

<properties>
    <hive.version>1.1.1</hive.version>
    <hadoop.version>2.7.1</hadoop.version>
</properties>

需要适配我们的CDH集群,在引入parquet时,我们更改了DATAX的hdfswriter中hive和hadoop版本:

<properties>
    <hive.version>1.1.0-cdh5.12.1</hive.version>
    <hadoop.version>2.6.0-cdh5.12.1</hadoop.version>
    <!--<hive.version>1.1.1</hive.version>-->
    <!--<hadoop.version>2.7.1</hadoop.version>-->
</properties>

所以,目测当时阿里开发人员在做文件类型支持时,肯定也是考虑过parquet了的,但是确实存在版本兼容问题,因此舍弃了parquet文件格式,而选择了新型的ORC。大家在使用时,如果出现版本问题,请调整hive和hadoop版本进行重新编译。

那么,我选新建插件模块,来绕开这个问题,于是新建了一个模块: hdfsparquetwriter 用来在支持parquet写入,具体代码仓库为:https://gitee.com/jackielee4cn/DataX.git

欢迎进行指正和Star 。

3.使用样例

3.1 编译安装

下载源码,编译打包,找到模块文件 的 target/datax/plugin/writer/hdfswriter.zip 文件。将文件解压到datax安装目录的${DATAX_HOME}/plugin/writer/ 下 。

3.2 配置datax job

配置方式与官网的orc根式hdfswriter方式一致,只是这里的fileType,只支持text、csv、rcfile、parquet ,不支持ORC (orc相关方法被注释掉了 ) ,并且wtier.name为【hdfsparquetwriter】,相当于是hdfswriter插件的一个补丁插件。

具体样例内容如下:

test_hdfswriter_parquet.job

{
  "core": {
    "transport": {
      "channel": {
        "speed": {
          "record": "100000"
        }
      }
    }
  },
  "job": {
    "setting": {
      "speed": {
        "channel": 2
      },
      "errorLimit": {
        "record": 0,
        "percentage": 0.02
      }
    },
    "content": [
      {
        "reader": {
          "name": "mysqlreader",
          "parameter": {
            "username": "write_user111",
            "password": "aaa@123",
            "column": [
				"`f_id`",
				"`f_order_id`",
				"`f_is_refund`",
				"`f_amount`",
				"`f_time`"
            ],
            "splitPk": "f_id",
			"where": "f_time<='2021-12-01 01:00:00' ", 
            "connection": [
              {
                "table": [
                  "test_datax_parquet"
                ],
                "jdbcUrl": [
                  "jdbc:mysql://test02:3306/test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&dontTrackOpenResources=true"
                ]
              }
            ]
          }
        },
        "writer": {
          "name": "hdfsparquetwriter",
          "parameter": {
            "defaultFS": "hdfs://test01:8020",
            "fileType": "parquet",
            "path": "/user/hive/warehouse/test.db/test_datax_parquet/date_id=20211201",
            "fileName": "test_datax_text",
            "writeMode": "append",
            "fieldDelimiter": "\u0001",
            "column": [
              {
                "name": "f_id",
                "type": "bigint"
              },
              {
                "name": "f_order_id",
                "type": "string"
              },
              {
                "name": "f_is_refund",
                "type": "int"
              },
              {
                "name": "f_amount",
                "type": "double"
              },
              {
                "name": "f_time",
                "type": "string"
              }
            ]
          }
        }
      }
    ]
  }
}

3.3 执行job

python ${DATAX_HOME}/bin/datax.py test_hdfswriter_parquet.job

查看控制台日志,执行正常。