列式存储格式之Parquet

列式存储

列式存储和行式存储相比有哪些优势呢?

  1. 可以跳过不符合条件的数据,只读取需要的数据,降低 IO 数据量。
  2. 压缩编码可以降低磁盘存储空间。由于同一列的数据类型是一样的,可以使用更高效的压缩编码(例如 Run Length Encoding 和 Delta Encoding)进一步节约存储空间。
  3. 只读取需要的列,支持向量运算,能够获取更好的扫描性能。

Parquet是什么

     Parquet的灵感来自于2010年Google发表的Dremel论文,文中介绍了一种支持嵌套结构的存储格式,并且使用了列式存储的方式提升查询性能,在Dremel论文中还介绍了Google如何使用这种存储格式实现并行查询的,如果对此感兴趣可以参考论文和开源实现Apache Drill。

      Parquet仅仅是一种存储格式,它是语言、平台无关的,并且不需要和任何一种数据处理框架绑定,目前能够和Parquet适配的组件包括下面这些,可以看出基本上通常使用的查询引擎和计算框架都已适配,并且可以很方便的将其它序列化工具生成的数据转换成Parquet格式。

       查询引擎: Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL
       计算框架: MapReduce, Spark, Cascading, Crunch, Scalding, Kite
       数据模型: Avro, Thrift, Protocol Buffers, POJOs

Parquet文件格式
       Parquet文件是以二进制方式存储的,所以是不可以直接读取的,文件中包括该文件的数据元数据,因此Parquet格式文件是自解析的。在HDFS文件系统和Parquet文件中存在如下几个概念。

      HDFS块(Block):它是HDFS上的最小的副本单位,HDFS会把一个Block存储在本地的一个文件并且维护分散在不同的机器上的多个副本,通常情况下一个Block的大小为256M、512M等。
      HDFS文件(File):一个HDFS的文件,包括数据和元数据,数据分散存储在多个Block中。
      行组(Row Group):按照行将数据物理上划分为多个单元,每一个行组包含一定的行数,在一个HDFS文件中至少存储一个行组,Parquet读写的时候会将整个行组缓存在内存中,所以如果每一个行组的大小是由内存大的小决定的,例如记录占用空间比较小的Schema可以在每一个行组中存储更多的行。
      列块(Column Chunk):在一个行组中每一列保存在一个列块中,行组中的所有列连续的存储在这个行组文件中。一个列块中的值都是相同类型的,不同的列块可能使用不同的算法进行压缩。
     页(Page):每一个列块划分为多个页,一个页是最小的编码的单位,在同一个列块的不同页可能使用不同的编码方式。

数据模型(先理解Parquet文件格式在理解数据模型)

      Parquet支持嵌套的数据模型,类似于Protocol Buffers,每一个数据模型的schema包含多个字段,每一个字段又可以包含多个字段,每一个字段有三个属性:重复数、数据类型和字段名,重复数可以是以下三种:required(出现1次),repeated(出现0次或多次),optional(出现0次或1次)。每一个字段的数据类型可以分成两种:group(复杂类型)和primitive(基本类型)。例如Dremel中提供的Document的schema示例,它的定义如下:

message Document {
    required int64 DocId;
    optional group Links {
        repeated int64 Backward;
        repeated int64 Forward;
    }
    repeated group Name {
        repeated group Language {
            required string Code;
            optional string Country;
        }
        optional string Url;
    }
}
这种schema模式其实是为了实现复杂数据类型如 list array等形式
required只能出现1次
repeated代表可能出现任意多次,但是如果出现0次则需要使用NULL标识
optional代表出现0次或1次

 

下面代码是从qarquet schema(包含元数据以及数据)获取元数据字段名以及字段类型(相当于sql定义表时表字段名以及字段类型

package com.didichuxing.fe.offline.util;
import com.didichuxing.fe.offline.entity.TableInfo;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.parquet.format.converter.ParquetMetadataConverter;
import org.apache.parquet.hadoop.ParquetFileReader;
import org.apache.parquet.hadoop.metadata.ParquetMetadata;
import org.apache.parquet.schema.MessageType;
import org.apache.parquet.schema.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;

public class ParquetShema {
    private static Logger logger = LoggerFactory.getLogger(ParquetShema.class);

    public static List<TableInfo> getSchema(Configuration conf, Path parquetInputPath){
        List<Type> result = new ArrayList<>();
        List<TableInfo> tableInfoList = new ArrayList<TableInfo>();
        TableInfo tableInfo = new TableInfo();
        try {
            conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
            FileSystem fs = FileSystem.get(conf);
            FileStatus fileList[] = fs.listStatus(parquetInputPath);
            FileStatus f = fileList[1];
            ParquetMetadata readFooter = null;
            readFooter = ParquetFileReader.readFooter(conf, f.getPath(), ParquetMetadataConverter.NO_FILTER);
            MessageType schema =readFooter.getFileMetaData().getSchema();
            logger.error(schema.toString());
            result = schema.getFields();
            logger.error(result.toString());

            if (result != null) {
                for (int i=0; i< result.size(); i++) {
//                    logger.error("column elem: " + result.get(i).getName()+",origin type:"
//                            + (result.get(i).getOriginalType().toString() == null ? "null" : result.get(i).getOriginalType().toString())
//                            + ", real type:" + (result.get(i).asPrimitiveType().getName() == null ? "null" : result.get(i).asPrimitiveType().getName()));
                    logger.info("column elem: " + result.get(i).getName());
                    tableInfo.setColumnName(result.get(i).getName());
                    logger.info("getPrimitiveTypeName: {}   getOriginalType: {} ", result.get(i).asPrimitiveType().getPrimitiveTypeName().name(), result.get(i).getOriginalType().toString());
                    if(result.get(i).getOriginalType().name() == null){
                         if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("BINARY")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.BINARY);
                        }else if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("BOOLEAN")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.BOOLEAN);
                        }else if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("DOUBLE")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.DOUBLE);
                        }else if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("FLOAT")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.FLOAT);
                        }else if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("INT32")){//name()方法获取枚举名字
                            tableInfo.setColumnType(ParquetToHiveTypeConst.INT32);
                        }else if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("INT64")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.INT64);
                        }else if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("INT96")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.INT96);
                        }else{
                             logger.error("OriginalType转换错误");
                         }
                    }else if(result.get(i).getOriginalType().name() != null){
                        if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("BINARY") && result.get(i).getOriginalType().name().equals("DECIMAL")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.DECIMAL);
                        }else if(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().equals("BINARY") && result.get(i).getOriginalType().name().equals("UTF8")){
                            tableInfo.setColumnType(ParquetToHiveTypeConst.BINARY);
                        }else{
                            tableInfo.setColumnType(result.get(i).asPrimitiveType().getPrimitiveTypeName().name().toLowerCase());
                        }
                    }else{
                        logger.error("PrimitiveType类型转换错误");
                    }
                    tableInfoList.add(tableInfo);
                }
            }

        } catch (Exception e) {
            logger.info(e.getMessage());
        }
        return tableInfoList;
    }
}

 

  字段类型里面有PrimitiveType以及OriginalType两种,具体怎么用可以查询这两种用途。



参考:https://blog.csdn.net/yu616568/article/details/50993491 (将parquet的值得好好看看)
posted @ 2019-12-20 14:37  远古大人  阅读(1244)  评论(0编辑  收藏  举报