MapReduce读定长文件写入Parquet文件


前言

MapReduce中读写文件一般都存储在HDFS上,所以需要读取的文件需要上传到HDFS文件系统中。


一、定义MapReduce

1.定义Job示例

定时时候需要注意

  1. InputFormat类的数据格式。其数据格式为Mapper中的输入格式。
  2. OutputFormat类的数据格式。其数据格式为Reduce中输出的格式。

定义Job代码如下(示例):

package com.study.spark.mr;

import com.study.spark.mr.mapper.FixedLengthMapper;
import com.study.spark.mr.utils.ParquetDataSchema;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FixedLengthInputFormat;
import org.apache.parquet.example.data.Group;
import org.apache.parquet.hadoop.ParquetOutputFormat;
import org.apache.parquet.hadoop.example.GroupWriteSupport;
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.schema.Type;

import java.util.ArrayList;
import java.util.List;

public class FileParquetExample {

    public void mr() throws Exception {
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);
        //设置执行的
        job.setJarByClass(FileParquetExample.class);
        job.setJobName("FileParquetExample");

        String struct = "当前这个文件的需要转出的数据格式,可以从文件中读取";
        configuration.set("data.struct", struct);

        job.setInputFormatClass(FixedLengthInputFormat.class);
        int recordLength = 100; //定长行长度
        FixedLengthInputFormat.setRecordLength(configuration, recordLength);
        FixedLengthInputFormat.setInputPaths(job, new Path("hdfs://input/data/text.dat.gz"));
        job.setMapOutputKeyClass(LongWritable.class);
        job.setMapOutputValueClass(BytesWritable.class);
        job.setMapperClass(FixedLengthMapper.class);

        job.setOutputFormatClass(ParquetOutputFormat.class);
        ParquetOutputFormat.setWriteSupportClass(job, GroupWriteSupport.class);
        GroupWriteSupport.setSchema(parquetSchema(), configuration);
        ParquetOutputFormat.setOutputPath(job, new Path("hdfs://output/data/text"));
        job.setOutputKeyClass(Void.class);
        job.setOutputValueClass(Group.class);

        //从文件提交到结束
        boolean isFinsh = job.waitForCompletion(true);
        //执行成功或是失败的处理

    }
	/**
	* 示例Parquet数据格式
	*/
    public MessageType parquetSchema() {
        List<Type> types = new ArrayList<>();
        types.add(ParquetDataSchema.stringTypeInfo("name", Type.Repetition.REQUIRED));
        types.add(ParquetDataSchema.intTypeInfo("age", Type.Repetition.OPTIONAL));
        types.add(ParquetDataSchema.decimalTypeInfo("deposit", Type.Repetition.REQUIRED, 24, 4));
        //数组类型,()里面参数时固定的
        Type arrayDataType = ParquetDataSchema.stringTypeInfo("array_element", Type.Repetition.OPTIONAL);
        types.add(ParquetDataSchema.arrayTypeInfo("friends", Type.Repetition.OPTIONAL, arrayDataType));
        //数组类型,()里面参数时固定的
        Type keyType = ParquetDataSchema.stringTypeInfo("key", Type.Repetition.OPTIONAL);
        Type valueType = ParquetDataSchema.intTypeInfo("value", Type.Repetition.OPTIONAL);
        types.add(ParquetDataSchema.mapTypeInfo("map", keyType, valueType));
        //测试直接使用,
        types.add(ParquetDataSchema.structTypeInfo("schema", types));
        return ParquetDataSchema.convert(types);
    }
}

2.Mapper

  1. 处理从InputFormat中读取到的数据。
  2. 其中输入数据类型为InputFormat的数据类型。
  3. 输出数据类型为Reduce中输入的数据类型。

定义Map示例代码

package com.study.spark.mr.mapper;

import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class FixedLengthMapper extends Mapper<LongWritable, BytesWritable, LongWritable, BytesWritable> {
    /**
     * 在这里完成,对数据的修改。如果不错修改也可以放到Reduce中进行修改
     */
    protected void map(LongWritable key, BytesWritable value, Context context) throws IOException, InterruptedException {
        context.write(key, value);
    }
}

3.Reduce

  1. 处理从Mapper中传输过来的数据。
  2. 将数据转换为输出文件需要的数据类型

定义Reduce示例代码

package com.study.spark.mr.reduce;

import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.parquet.example.data.Group;
import org.apache.parquet.example.data.simple.SimpleGroupFactory;
import org.apache.parquet.hadoop.example.GroupWriteSupport;
import org.apache.parquet.schema.MessageType;
import scala.collection.parallel.ParIterableLike;

import java.io.IOException;


public class ParquetReduce extends Reducer<LongWritable, BytesWritable,Void, Group> {
    private SimpleGroupFactory simpleGroupFactory;

    /**
     * 在任务开始时,初始化。用于定义reduce方法中需要用到的数据。
     */
    protected void setup(Context context) throws IOException, InterruptedException {
       MessageType messageType = GroupWriteSupport.getSchema(context.getConfiguration());
       simpleGroupFactory =new SimpleGroupFactory(messageType);
       //可以在这里获取定长文件,每个字段的起始位和结束位。转为在reduce方法中容易读取的对象
       String struct = context.getConfiguration().get("data.struct");

    }

    /**
     * 循环Mapper中读取到的数据
     */
    @SuppressWarnings("unchecked")
    protected void reduce(LongWritable key, Iterable<BytesWritable> values, Context context
    ) throws IOException, InterruptedException {

        for(BytesWritable value: values) {
            byte[] bytes = value.getBytes();
            //根据自己需要,截取定长文件。
            Group group =  simpleGroupFactory.newGroup();
            //截取到的数据。根据定义的Schema。指定以输出
            //如:group.add("age",ParquetDataWrite.intWriter(new Integer(10)))
            //group.add(2,ParquetDataWrite.stringWriter("名称"));
            context.write(null,group);
        }
    }
}


二、Parquet数据格式

1 定义输出Parquet文件格式

因Parquet数据是有格式的,需要将定义每行数据格式
Repetition.REQUIRED 字段必须有对应的值 必填字段可以采用当前格式
Repetition.OPTIONAL 字段可以有对应的值如果不是必填项可以采用当前格式
Repetition.REPEATED 字段对应值可以重复

/**
* 定义Parquet格式
*/
package com.study.spark.mr.utils;

import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe;
import org.apache.parquet.schema.*;
import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
import org.apache.parquet.schema.Type.Repetition;

import java.util.List;

/**
 * 定义Parquet Schema格式
 * GroupType是Type的子类
 */
public class ParquetDataSchema {

    public static MessageType convert(String schemaName, final List<Type> types) {
        final MessageType schema = new MessageType(schemaName, types);
        return schema;
    }

    public static MessageType convert(final List<Type> types) {
        return convert("hive_schema", types);
    }

    /**
     * 支持数据类型:String,Char, VarChar
     */
    public static Type stringTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.BINARY, repetition).as(OriginalType.UTF8).named(name);
    }

    /**
     * 支持数据类型:int
     */
    public static Type intTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.INT32, repetition).named(name);
    }

    /**
     * 支持数据类型:short
     */
    public static Type shortTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.INT32, repetition)
                .as(OriginalType.INT_16).named(name);
    }

    /**
     * 支持数据类型:byte
     */
    public static Type byteTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.INT32, repetition)
                .as(OriginalType.INT_8).named(name);
    }

    /**
     * 支持数据类型:long
     */
    public static Type longTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.INT64, repetition).named(name);
    }

    /**
     * 支持数据类型:double
     */
    public static Type doubleTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.DOUBLE, repetition).named(name);
    }

    /**
     * 支持数据类型:float
     */
    public static Type floatTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.FLOAT, repetition).named(name);
    }

    /**
     * 支持数据类型:boolean
     */
    public static Type booleanTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.BOOLEAN, repetition).named(name);
    }

    /**
     * 支持数据类型:byte[]
     */
    public static Type binaryTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.BINARY, repetition).named(name);
    }

    /**
     * 支持数据类型:timestamp
     */
    public static Type timestampTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.INT96, repetition).named(name);
    }

    /**
     * 支持数据类型:decimal
     *
     * @param prec  数据长度
     * @param scale 小数点数
     */
    public static Type decimalTypeInfo(String name, Repetition repetition, int prec, int scale) {
        int bytes = ParquetHiveSerDe.PRECISION_TO_BYTE_COUNT[prec - 1];
        return Types.optional(PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY).length(bytes).as(OriginalType.DECIMAL).
                scale(scale).precision(prec).named(name);
    }

    /**
     * 支持数据类型:date
     */
    public static Type dateTypeInfo(String name, Repetition repetition) {
        return Types.primitive(PrimitiveTypeName.INT32, repetition).as(OriginalType.DATE).named(name);
    }

    /**
     * 支持数据类型:Array,List
     *
     * @param subType: 表示数据类型如 stringTypeInfo("array_element",Repetition.OPTIONAL)
     *                 在使用的时候只要修改一下数组的数据类型即可,后面参数保持一致
     */
    public static GroupType arrayTypeInfo(final String name, Repetition repetition, Type subType) {
        return new GroupType(repetition, name, OriginalType.LIST, new GroupType(Repetition.REPEATED,
                ParquetHiveSerDe.ARRAY.toString(), subType));
    }

    /**
     * 支持数据类型:Map
     *
     * @param keyType   表示键数据类型如 stringTypeInfo("key",Repetition.OPTIONAL)
     * @param valueType 表示值数据类型如 stringTypeInfo("value",Repetition.OPTIONAL)
     *                  在使用的时候只要修改一下数组的数据类型即可,后面参数保持一致
     */
    public static GroupType mapTypeInfo(String name, Type keyType, Type valueType) {
        return ConversionPatterns.mapType(Repetition.OPTIONAL, name, keyType, valueType);
    }

    /**
     * 支持数据类型:Struct
     *
     * @param types 如:new ArrayList(stringTypeInfo("name",Repetition.OPTIONAL), intTypeInfo("age",Repetition.OPTIONAL));
     */
    public static GroupType structTypeInfo(final String name, List<Type> types) {
        return new GroupType(Repetition.OPTIONAL, name, types);
    }
}

其中Category数据类别

public static enum Category {
        PRIMITIVE, //原始数据或是基础数据类别
        LIST, //数组
        MAP, //数据 key,value形式的
        STRUCT, //数据是
        UNION;

        private Category() {
        }
    }

测试数据示例

public MessageType parquetSchema(){
        List<Type> types = new ArrayList<>();
        types.add(ParquetDataSchema.stringTypeInfo("name", Type.Repetition.REQUIRED));
        types.add(ParquetDataSchema.intTypeInfo("age", Type.Repetition.OPTIONAL));
        types.add(ParquetDataSchema.decimalTypeInfo("deposit", Type.Repetition.REQUIRED,24,4));
        //数组类型,()里面参数时固定的
        Type arrayDataType = ParquetDataSchema.stringTypeInfo("array_element",Type.Repetition.OPTIONAL);
        types.add(ParquetDataSchema.arrayTypeInfo("friends", Type.Repetition.OPTIONAL,arrayDataType));
        //数组类型,()里面参数时固定的
        Type keyType = ParquetDataSchema.stringTypeInfo("key",Type.Repetition.OPTIONAL);
        Type valueType = ParquetDataSchema.intTypeInfo("value",Type.Repetition.OPTIONAL);
        types.add(ParquetDataSchema.mapTypeInfo("map",keyType,valueType));
        //测试直接使用,
        types.add(ParquetDataSchema.structTypeInfo("schema",types));
        return ParquetDataSchema.convert(types);
    }

执行结果说明
在这里插入图片描述

2 Parquet数据写出

写入数据的定义

package com.study.spark.mr.utils;

import org.apache.hadoop.hive.common.type.HiveDecimal;
import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe;
import org.apache.hadoop.hive.ql.io.parquet.timestamp.NanoTimeUtils;
import org.apache.hadoop.hive.serde2.io.DateWritable;
import org.apache.parquet.io.api.Binary;

import java.sql.Date;
import java.sql.Timestamp;

public class ParquetDataWrite {

    public static Boolean booleanDataWriter(Boolean val) {
        return val;
    }

    public static Integer byteDataWriter(byte val) {
        return new Integer(val);

    }

    public static Integer shortDataWriter(Short val) {
        return new Integer(val);
    }

    public static Integer intWriter(Integer val) {
        return val;
    }

    public static Long longWriter(Long val) {
        return val;
    }

    public static Float floatWriter(Float val) {
        return val;
    }

    public static Double doubleDataWriter(Double val) {
        return val;
    }

    public static Binary stringWriter(String val) {
        return Binary.fromString(val);
    }

    public static Binary varcharWriter(String val) {
        return Binary.fromString(val);
    }

    /**
     * 将byte[]数据转为Binary,用于写入
     */
    public static Binary binaryWrite(byte[] bytes) {
        return Binary.fromByteArray(bytes);
    }

    /**
     * 将时间戳Timestamp转为Binary,用于写入
     */
    public static Binary timestampWrite(Timestamp ts) {
        return NanoTimeUtils.getNanoTime(ts, false).toBinary();
    }

    /**
     * 将字符串Decimal数据转为Binary,用于写入使用
     *
     * @param val   数据值
     * @param prec  定义Decimal中的数据长度
     * @param scale 定义Decimal中小数点后面位数
     */
    public static Binary decimalWrite(String val, int prec, int scale) {
        HiveDecimal hiveDecimal = HiveDecimal.create(val);
        byte[] decimalBytes = hiveDecimal.bigIntegerBytesScaled(scale);

        // Estimated number of bytes needed.
        int precToBytes = ParquetHiveSerDe.PRECISION_TO_BYTE_COUNT[prec - 1];
        if (precToBytes == decimalBytes.length) {
            // No padding needed.
            return Binary.fromByteArray(decimalBytes);
        }

        byte[] tgt = new byte[precToBytes];
        if (hiveDecimal.signum() == -1) {
            // For negative number, initializing bits to 1
            for (int i = 0; i < precToBytes; i++) {
                tgt[i] |= 0xFF;
            }
        }

        System.arraycopy(decimalBytes, 0, tgt, precToBytes - decimalBytes.length, decimalBytes.length); // Padding leading zeroes/ones.
        return Binary.fromByteArray(tgt);
    }

    /**
     * 将Date数据类型转为Int
     */
    public static Integer dateWrite(Date date) {
        return Integer.valueOf(DateWritable.dateToDays(date));
    }
     /**
     * list 数据类型转为Group
     * @param group  主结构体
     * @param index 为当前数据在结构体中的位置,也可以传入字段名称
     * @param values  数组中的值,这里String只是示例,具体根据List立民安数据类型写入
     * @return
     */
    public static Group listWrite(Group group, int index,List<String> values){
        Group listGroup = group.addGroup(index);
        for(String v : values){
            Group bagGroup = listGroup.addGroup(0);
            bagGroup.add(0,v);
        }
        return group;
    }
    /**
     * map 数据类型转为Group
     * @param group  主结构体
     * @param index 为当前数据在结构体中的位置,也可以传入字段名称
     * @param values  map中Key和value只是示例,具体根据定义Map结构传入
     */
    public static Group mapWrite(Group group, int index, Map<String,String> values){
        Group mapGroup = group.addGroup(index);
        Iterator<String> iterable =  values.keySet().iterator();
        while (iterable.hasNext()){
            String key = iterable.next();
            String value = values.get(key);
           Group dataGroup =  mapGroup.addGroup(0);
           dataGroup.add("key",key);
           dataGroup.add("value",value);
        }
        return group;
    }

    /**
     * Struct 结构转为Group
     * @param group 主结构体
     * @param index 为当前数据在结构体中的位置,也可以传入字段名称
     * @param values 这里为示例,具体根据定义结构传入
     * @return
     */
    public static Group structWrite(Group group, int index,String[] values){
        Group structGroup =group.addGroup(index);
        for(int i = 0; i < values.length; i++){
            structGroup.add(i,values[i]);
        }
        return group;

    }

}



总结

  1. 一般将文件格式定义好,在传入HDFS文件中读取。
  2. 读取文件时候需要注意文件格式。
  3. 每种读取类型有其特定需要配置的参数需要记得配置
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值