前言
MapReduce中读写文件一般都存储在HDFS上,所以需要读取的文件需要上传到HDFS文件系统中。
一、定义MapReduce
1.定义Job示例
定时时候需要注意
- InputFormat类的数据格式。其数据格式为Mapper中的输入格式。
- 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
- 处理从InputFormat中读取到的数据。
- 其中输入数据类型为InputFormat的数据类型。
- 输出数据类型为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
- 处理从Mapper中传输过来的数据。
- 将数据转换为输出文件需要的数据类型
定义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;
}
}
总结
- 一般将文件格式定义好,在传入HDFS文件中读取。
- 读取文件时候需要注意文件格式。
- 每种读取类型有其特定需要配置的参数需要记得配置