• 列式存储格式之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的值得好好看看)
  • 相关阅读:
    Nginx简单认识
    Redis简单入门认识
    用户体验报告——脉脉
    zine结构图
    猫眼电影原型图
    关于共享单车的一点思考
    用户体验报告——网易严选
    Zine和石墨文档竞品分析
    用户体验报告——石墨文档
    集合框架2
  • 原文地址:https://www.cnblogs.com/yangcao/p/12072976.html
Copyright © 2020-2023  润新知