hadoop

1,054 阅读24分钟

一、Hadoop篇

1、hadoop分布式存储

1)hadoop架构

hadoop架构由三部分组成:分布式存储HDFS、分布式计算MapReduce、资源调度引擎Yarn。hadoop历史演变如下图所示:

2)Hdfs架构

大多数的大数据架构都是主从架构,HDFS也是主从架构Master|Slave或称为管理节点|工作节点

3)Secondary原理

1、原数据存储在NameNode的内存会有什么问题?有什么功能解决?

当系统出现故障原数据会丢失。 HDFS编辑日志文件editlog:在NameNode节点中的编辑日志editlog中,记录下来对客户端对HDFS的所有记录。如:增、删、重命名等。

但是editlog日志文件大小会随着时间的越来越大,导致系统重启根据日志恢复的时间越来越长,为了避免这种情况,hdfs引入checkpoint检查点机制,命名空间fsimage就是hdfs原数据的持久性检查点,即将内存中的元数据落磁盘生成的文件。

此时如果namenode重启,可以将磁盘中fsimage文件读入内容,将数据恢复到某一个检查点,在执行检查点之后的编辑日志,最后完全恢复。但是依然,随着时间的推移,editlog日志会变多,再namenode重启的时候会花费很多事件且hdfs不可用,为了解决这一问题,hdfs引入了secondarynamenode,辅助namenode用来合并fsimage及editlog

2、secondarynamenode流程 前提:

  • 创建checkpoint检查点的两大条件是:
    • SecondaryNameNode每隔一个小时创建一个检查点
    • SecondaryNameNode每一分钟检查一次,从上一个检查点开始,editlog文件中是否包含了1万个事物,如果是也会创建检查点。 流程:
  • SecondaryNameNode首先请求原namenode的进行edits的滚动,这样新的编辑日志会进入到新的文件。
  • SecondaryNameNode然后通过HTTP GET方式读取原NameNode的fsimage以及edits
  • SecondaryNameNode读取fsimage到内存中。然后执行edits的每个操作。并创建一个新的fsimage文件
  • SecondaryNameNode通过HTTP PUT方将新的fsimage文件发送到原来的NameNode
  • 原来的NameNode用新的fsimage替换老的fsimage,同时系统会更新fsimage文件到记录检查点的时间。
  • 过程结束后,namenode会有最新的fsimage和最小的edits

4)机架感知存储原理

在机房中,会有机架,每个机架会有若干个服务器: 第一块会在本机器的HDFS目录下存储block的第一个副本 第二块在不同Rack(机架)的某个DataNode(d2)上存储Block的第二个副本 第三块在d2所在的机架上,找到其他一台其他的datanode节点,存储block的第三个副本 更多副本,随机节点

5)心跳机制

工作原理:

  • NameNode启动的时候会启动一个ipc server在里面
  • DataNode启动后会向NameNode注册,每个3s会向NameNode发送一个心跳heartbeat
  • 心跳返回结果带有NameNode给DataNode命令,如复制块数据到另一个DataNode,或者删除数据块
  • 如果超过10分钟NameNode没有收到某个DataNode的心跳,则认为DataNode节点不可用
  • DataNode周期性(6小时)的向namenode上报当前的块状态报告BlockReport。块状态报告包含了一个该DataNode上所有数据块的列表 作用:
  • 通过心跳nameNode向DataNode返回指令
  • 可以判断DataNode是否在线
  • 通过BlockReport,namenode能够知道各个datanode的存储情况,如磁盘的利用率、块列表。和负载均衡有关
  • hadoop集群刚开始启动的时候,99.9%的block没有达到最小副本数,集群处于安全模式,涉及BlockReport

6)hadoop的读写流程:

7)hadoop写流程

详细流程:

  • 创建文件:
    • HDFS客户端向HDFS写数据,先调用DistributedFileSystem.create()方法,在HDFS创建新的文件
    • RPC(ClientProtocol.create())远程调用NameNode(NameNodeRpcServer)的create(),首先在HDFS目录树指定路径添加新的文件
    • 然后将创建新文件的操作记录在editslog中
    • NameNode.create方法执行完后,DistributedFileSysrem.create()返回FSDataOutputStream,他的本质是封装一个DFSOutputStream对象。
  • 建立数据流管道:
    • 客户端调用DFSOutputStream.write()写数据
    • DFSOutputStream调用ClientProtocol.addBlock()首先向NameNode申请一个空的数据块
    • addBlock()返回LocatedBlock对象,对象包含当前数据块的所有datanode的位置信息。
    • 通过位置信息建立数据流管道
  • 向数据流管道popeline中写当前块的数据:
    • 客户端向流管道中写数据,先将数据写入一个检验块chunk中,大小512Byte,写满后,计算chunk的校验和checksum值(4Byte)
    • 然后chunk数据本身加上checksum,形成一个新的带有checksum值的chunk(516byte)
    • 保存到一个更大一些的结构packet数据包中,packet为64kb大小
    • packet写满后,先被写入一个dataqueue队列中
    • oascket被从队列中取出,向pipeline中写入,先写入datanode1,再从datanode1传到datanode2,在从datanode2传到datanode3中
    • 一个packet数据取完后,后被放入到ackQueue中等待pipeline关于packet的ack反馈
      • 每个packet都有ack确认包,逆pipeline(dn3-》dn2-》dn1)传回数据流
    • 若packet的ack是success成功的,则从ackQueue中,将packet删除;否则,将packet从ackQueue中取出,从新放入dataQueue,重新发送 -如果当前块写完后,文件还有其他块还要写,那么继续调用ackBlock方法
    • 文件最后一个block块数据写完后,会发再发送一个空的packet,表示block写完了,然后关闭pipeline,所有块写完close()关闭流
    • ClientProtocol.complete()通知namenode当前文件所有块写完了

容错

  • 在写的过程中pipeline中的datanode出现故障,输出流如何恢复
    • 数据流中的ackQueue缓存的所有packet会被重新加入dataQueue
    • 输出流调用ClientProtocol.updateBlockForPipeline(),为block申请一个新的时间戳,namenode会记录新时间戳
    • 确保故障datanode即使恢复,但后于其上的block时间戳与namenode记录的新的时间戳不一致,故障datanade上的block进而被删除,故障的datanode从pipeline删除
    • 输出流调用ClientProtocol.getAdditionalDatanode()同志namenode分配新的datanode到数据流pipeline中,并使用新的时间戳建立pipeline
    • 新添加到pipeline中的datanode,目前还没有存储这个新的block,HDFS客户端通过DataTransferProtocol通知pipeline中的一个datanode复制这个block到新的datanode中
    • pipeline重建后,输出流调用ClientProtocol.updatePipeline(),更新namenode中的原数据
    • 故障恢复完毕,完成后续的写入

8)hdfs读流程原理

hdfs读流程原理和写数据流程类似

  • 客户端读取hdfs文件,client调用文件系统对象的DistributedFileSystem的open方法
  • 返回FSDataInputStream对象
  • 构造DFSInputStream对象时,调用namenode的getBlockLocations的方法,获取file的开始若干block的存储datanode列表;针对每个block的dn列表,会根据网络拓扑排序,离client近的排在前面
  • 调用DFSInputStream的read方法,先读取block1的数据,与client最近的datanode建立连接,读取数据。
  • 读取完后,关闭与dn建立的流
  • 读取下一个block2的数据
  • 文件数据读取后,调用FSDataInputStream的close方法

容错:

  • 情况一:读取block过程中,client与datanode通信终端
    • client与存储此block的第二个datanode建立链接,读取数据
    • 记录此有问题的datanode,不会从它上读取数据
  • 情况二:client读取block发现block数据有问题
    • client读取block数据时,同时会读取到block的校验和,若client针对读取过来的block数据,计算校验和,其值与读取过来的校验和不一样说明block数据损坏
    • client从存储此block副本的其他datanode上读取block数据(也会计算校验和)
    • 同时client告知nodename此情况

9)HA高可用

HDFS高可用: 对于HDFS,NN存储元数据在内存中,并负责管理文件系统的命名空间和客户端对HDFS的读写请求,但是只有一个NN,一旦发生单点故障,会是整个集群系统失效,虽然有SNN,但是它并不是NN的热备份。无法立即切换SNN对外提供服务,即HDFS处于停服状态。

  • hdfs2.x采用HA架构:

    • 在HA集群中,可设置两个NN,一个处于活跃状态,另一个处于待命状态
    • 由zookeeper确保一主一备
    • 处于active状态的NN负责响应所有客户端的请求,处于standby状态的nn作为热备份节点,保证与active的NN的原数据同步
    • ACTICT节点发生故障的时候,zookeeper集群会发现此情况,同志standby节点立即切换到活跃状态对外提供服务
    • 确保集群一直处于可用状态
  • 如何热备份数据

    • standby是active NN的热备份,因此active NN的状态信息必须实时同步到StandbyNN
    • 可以借助一个共享存储系统来实现状态同步,如NFS(NetworkFile System)、QJM(Quorum Journal Manager)或者Zookeeper。
    • Active NN将更新数据写到共享存储系统,Standby NN一直监听该系统,一旦发现有新的数据写入,就立即从公共存储系统中读取这些数据并加载到standby nn自己内存中,从而保证与活跃的nn数据一致
  • 块报告

    • NN保证了数据块到实际存储位置的映射关系,为了实现故障时的快速切换,必须保证StandbyNN中也包含最新的块映射关系
    • 因此需要给所有的dn配置Active和Standby两个NN的地址,把块的位置和心跳信息同时发送到两个NN上。

10)联邦(了解)

1、为什么需要联邦 虽然ha解决了单点古钟问题,但是hdfs在扩展性、整体性能和隔离性方面仍然存在问题

  • 系统扩展方面,元数据存储在nn内存中,受限于内存上线(每个文件、目录、block占用约150字节)
  • 整体性能方面,吞吐受单个nn的影响
  • 隔离性方面,一个程序可能会影响其他程序的运行,如果一个程序消耗过多资源会导致其他程序无法顺利运行
  • ha本质还是单名称节点 2、联邦解决以上3个问题
  • hdfs联邦中,设计了多个命名空间,每个命名空间有一个nn或一主一备两个nn,使得hdfs的命名服务能够水平扩展
  • 这些nn分别进行各自命名空间namespace和块管理相互独立,不需要彼此协调
  • 每个DN要向集群中所有的NN注册,并周期性的向所有NN发送心跳信息和块信息,报告自己的状态
  • HDFS联邦每个相互独立的NN对应一个独立的命名空间
  • 每一个命名空间管理属于自己的一组块,这些属于同一命名空间的块对应一个“块池”的概念。
  • 每个DN会为所有块池提供块的存储,块池中的各个块实际上是存储在不同DN中的

11)压缩(了解)

  • 文件压缩好处:

    • 减少数据所占用的磁盘空间
    • 加快数据在磁盘、网络上的IO
  • 常用压缩格式

    压缩格式UNIX工具算 法文件扩展名可分割
    DEFLATEDEFLATE.deflateNo
    gzipgzipDEFLATE.gzNo
    zipzipDEFLATE.zipYES
    bzipbzip2bzip2.bz2YES
    LZOlzopLZO.lzoNo
    SnappySnappy.snappyNo
  • Hadoop的压缩实现类;均实现CompressionCodec接口

    压缩格式对应的编码/解码器
    DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
    gziporg.apache.hadoop.io.compress.GzipCodec
    bzip2org.apache.hadoop.io.compress.BZip2Codec
    LZOcom.hadoop.compression.lzo.LzopCodec
    Snappyorg.apache.hadoop.io.compress.SnappyCodec

11)小文件治理(了解)

Sequence Files方案:

  • SequenceFile文件,主要由一条条record记录组成;每个record是键值对形式的
  • SequenceFile文件可以作为小文件的存储容器;
    • 每条record保存一个小文件的内容
    • 小文件名作为当前record的键;
    • 小文件的内容作为当前record的值;
    • 如10000个100KB的小文件,可以编写程序将这些文件放到一个SequenceFile文件。
  • 一个SequenceFile是可分割的,所以MapReduce可将文件切分成块,每一块独立操作。
  • 具体结构(如下图):
    • 一个SequenceFile首先有一个4字节的header(文件版本号)
    • 接着是若干record记录
    • 记录间会随机的插入一些同步点sync marker,用于方便定位到记录边界
  • 不像HAR,SequenceFile支持压缩。记录的结构取决于是否启动压缩
    • 支持两类压缩:
      • 不压缩NONE,如下图
      • 压缩RECORD,如下图
      • 压缩BLOCK,①一次性压缩多条记录;②每一个新块Block开始处都需要插入同步点;如下图
    • 在大多数情况下,以block(注意:指的是SequenceFile中的block)为单位进行压缩是最好的选择
    • 因为一个block包含多条记录,利用record间的相似性进行压缩,压缩效率更高
    • 把已有的数据转存为SequenceFile比较慢。比起先写小文件,再将小文件写入SequenceFile,一个更好的选择是直接将数据写入一个SequenceFile文件,省去小文件作为中间媒介.

2、MapReduce分布式计算

1)MapReduce原理

mapreduce是采用一种分而治之的思想涉及出来的分布式计算框架,由两部分组成,map阶段(切分成一个个小的任务)、reduce阶段(汇总小文件的结果)。

  • Map阶段(单词计数为例)
    • 假设mr的输入文件“gone with the wind”有是哪个block;block1、block2、block3
    • Mr编程的时,每个block对应一个map任务(map task)
    • 以第一个为例进行分析:
      • map1读取block1的数据,一次读取block1的一行数据;
      • 产生键值对(key/value),作为map()参数的入参,调用map()方法
      • 假设读取的是第一行,将当前所读行的行首相对于当前block开始处的字节偏移量作为key(0),当前行的内容作为value
      • 在map()内,将value当前行内容按空格切分,得到是哪个单词Dear | Bear | River
      • 将单词变成键值对,输出出去(Dear, 1) | (Bear, 1) | (River, 1);最终结果写入map节点所在的本地磁盘中
      • 第一行数据被处理后,接着处理第二行,逻辑同上,当block中所有的数据处理完后,此map任务结束
  • Reduce阶段
    • Reduce任务的个数由自己写的程序编程指定,每一个reduce逻辑一样,以第一个为例:
      • map1任务完成后,reduce1通过网络,连接到map1,将map1输出结果中属于reduce1的分区的数据,通过网络获取到reduce1端
      • 同样也连接到map2、map3获取结果。最终reduce1端获取4个(dear,1)键值对,由于key键相同,他们分到同1组,4个(dear,1)键值对,转换成[Dear, Iterable(1, 1, 1, )],作为两个入参传入到reduce()
      • 在reduce()内部,计算dear的总数为4,并将(dear,4)作为键值对输出
      • 每个reduce任务最终输出文件,文件写入到HDFS

shuffle简图 shuffle细节图

2)shuffle

shuffle主要是map端输出作为reduce端输入的过程

  • map端
    • 每个map任务都有一个对应的环形缓冲区;输出是kv对,先写入到环形缓冲区(默认大小:100m),当内容占据80%缓冲区空间后,有一个后台线程将缓冲区中的数据溢出写到一个磁盘文件
    • 在溢出写的过程中,map任务可以继续向环形缓冲区写入数据;但是若写入速度大于溢出写的数据,最终造成100m占满后,map任务会暂停向环形缓冲区写数据的过程,只执行溢出写的过程,直到环形缓冲区的数据全部溢出写到磁盘,才恢复向缓冲区写入
    • 后台线程溢写磁盘过程,有以下步骤:
      • 先对每个溢写的kv对做分区;每个分区的个数由mr程序的reduce任务数决定,默认使用HashPartitioner计算当前的kv对属于哪个分区,计算公式:(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks
      • 每个分区中根据kv对的key做内存中排序
      • 若设置了map端本地聚合combiner,则对每个分区中,排好序的数据做combine操作
      • 若设置了对map输出压缩功能,会对溢写数据压缩
    • 随着不断的向环形缓冲区写入数据,会多次触发溢写,本地磁盘最终会生成多个溢出文件。在maptask完成之前,所有溢出文件会被合并成一个大的溢出文件;且是一分区,已排序的输出文件(细节:1、在合并溢写文件时,如果至少有3个溢写文件,并且设置了map端combine的话,会在合并的过程中触发combine操作。2、但是若只有2个或者1个溢写文件,则不触发combine操作,因为其本质是一个reduce,需要启动jvm虚拟机,有一定的开销)
  • reduce端 -reduce teak会在每个map task运行完成后,通过HTTP获得map task输出中,属于自己的分区数据(很多kv对)
    • 如果map输出数据比较小,现保存在reduce的jmv内存中,否则直接写入reduce磁盘

    • 一旦内存缓冲区达到阈值(默认0.66)或者map输出数的阈值(默认1000),则触发归并merge,结果写到本地磁盘

    • 若mr编程指定combine,在归并的时候执行combine操作

    • 随着溢出写的文件增多,后台的线程会将他们合并大的、排好序的文件

    • reduce task将所有map task 复制完后,将合并磁盘上所有的溢出文件

    • 默认一次合并10个

    • 最后一批合并,部分数据来自内存,部分来自磁盘上的文件

    • 进行归并、排序、分组阶段

    • 每组数据调用一次reduce方法

Shuffle的大致流程为:Maptask会不断收集我们的map()方法输出的kv对,放到内存缓冲区中,当缓冲区达到饱和的时候(默认占比为0.8)就会溢出到磁盘中,如果map的输出结果很多,则会有多个溢出文件,多个溢出文件会被合并成一个大的溢出文件,在文件溢出、合并的过程中,都要调用partitoner进行分组和针对key进行排序(默认是按照Key的hash值对Partitoner个数取模),之后reducetask根据自己的分区号,去各个maptask机器上取相应的结果分区数据,reducetask会将这些文件再进行合并(归并排序)。

合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程(从文件中取出每一个键值对的Group,调用UDF函数(用户自定义的方法))

三、yarn调度

  • application在yarn执行过程中,可以总结为3步
    • 应用程序的提交
      • 客户端程序向rm提交应用并请求一个application实例
    • 启动app master实例
      • rm找到一个可以运行一个contain的nm并在这个container启动这个app master实例
      • app master向rm注册,注册之后用户就可以查询rm获得自己application的信息,以后就可以直接和自己的appmaster交互了
      • appmaster 根据resource-request向rm发送resource-request请求
      • 当container成功分配后,appmaster通过向nm发送container-launch-specification信息来启动container,container-launch-specification信息包含了能够让container和app master交流的材料
    • app master实例管理应用程序执行
      • 应用程序以task形式在启动的container中运行,并把运行的进程、状态等信息通过application-specific协议发送给app master
      • 在应用程序运行期间,客户端主动和app master交流获得应用的运行状态、进度更新等信息
      • 应用程序执行完成后并且所有工作也已经完成后,appmaster向rm取消注册然后关闭,用到所有的container也归还给系统

精简版:

  • 1、用户将应用程序提交到rm上
  • 2、rm向app master申请资源,并与某个nm通信,启动第一个container,以启动app master
  • 3、appmaster和rm进行注册通信,为内部要执行的任务申请资源,一旦得到资源后,将于nm通信,已启动对应的task
  • 4、所有任务完成后,appmaster向rm注销,整个应用程序运行结束

mapreduce on yarn

  • 提交任务

    • 程序打包成jar包,在客户端运行hadoop jar命令,提交job到集群运行
    • job.waitForCompletion(true)中调用Job的submit(),此方法中调用JobSubmitter的submitJobInternal()方法;
      • ②submitClient.getNewJobID()向resourcemanager申请一个mr作业ID
      • 检查输出目录:如果没有输出目录或者已存在则报错
      • 计算任务分片。若无法计算也会报错
      • ③运行作业的相关任务,如果作业的jar包、配置文件、输入分片。被上传到HDFS上一个以作业ID命名的目录(jar包副本默认为10,运行作业的任务,如map任务、reduce任务时,可从这10个副本读取jar包)
      • ④调用rm中的submitapplication来提交作业
    • 客户端每秒查询下作业的进度,作业如有变化则在控制台打印进度报告
    • 作业如果成果则打印相关计数器
    • 作业失败则打印失败原因
  • 初始化作业

    • 当rm收到submitApplication()的调用通知后,请求传递给rm的调度器(scheduler)调度器分配container
    • ⑤a rm与指定的nm通信,通知nm启动容器,nm收到通知后,创建特定资源的container
    • ⑤b 然后再container中运行appmaster
    • ⑥appmaster 需要接受任务的进度,完成报告,所以appmaster需要创建多个薄记对象来记录对象
    • ⑦从hdfs获取client计算输入分片split
      • 每个分片split创建一个map任务
      • 通过 mapreduce.job.reduces 属性值(编程时,jog.setNumReduceTasks()指定),知道当前mr需要创建多少个reduce任务
      • 每个任务有taskid
  • task任务分配

    • 如果是小作业,appmaster会以uberized的方式运行此mr作业,appmaster会决定他在jvm中顺序此mr的任务
      • 原因,若每个任务运行在一个单独的jvm时候,都需要单独启动jvm,分配资源需要时间。多个jvm中的任务在各自的jvm并行运行
      • 若将所有任务在appmaster中顺序执行的话更高效,那么appMaster就会这么做 ,任务作为uber任务运行
      • 小任务判断:小于10个map任务、只有一个reduce任务、mr输入大小小于一个hdfs块大小
      • 在运行任何task任务之前,appMaster调用setupJob()方法,创建OutputCommitter,创建作业的最终输出目录(一般为HDFS上的目录)及任务输出的临时目录(如map任务的中间结果输出目录)
    • 如果作业不是以uber任务方式运行,那么appmaster会为作业中的每一个任务向rm申请container
      • 由于reduce在进入排序阶段之前,所有的map任务必须执行完,所有map任务所需要申请的容器要优先reduce申请的容器
      • 5%的map的任务执行完成后,才会为reduce申请容器
      • 为map任务申请容器时,遵循数据本地化,调度器尽量将容器调度在map任务的输入分片所在的节点上(移动计算,不移动数据)
      • reduce任务能在集群任意计算节点运行
      • 默认情况下,为每个map任务、reduce任务分配1G内存、1个虚拟内核,由属性决定mapreduce.map.memory.mb、mapreduce.reduce.memory.mb、mapreduce.map.cpu.vcores、mapreduce.reduce.reduce.cpu.vcores
  • task任务执行

    • 当调度为当前的任务分配一个nm的容器,并将此信息传递appmaster后;appmaster与nm01通信,告知nm01启动一个容器,并此容器占定特定的资源量
    • NM01收到消息后,启动容器,此容器占据指定的资源量
    • 容器中运行YarnChild,由YarnChild运行当前任务(map、reduce)
  • 作业运行进度与状态更新

    • 作业job以及它的每个task都有状态(running、successfully completed、failed),当前任务的运行进度、作业计数器
    • 任务在运行期间,每隔3秒向appMaster汇报执行进度、状态(包括计数器)
    • appMaster汇总目前运行的所有任务的上报的结果
    • 客户端每个1秒,轮询访问appMaster获得作业执行的最新状态,若有改变,则在控制台打印出来
  • 完成作业

    • appMaster收到最后一个任务完成的报告后,将作业状态设置为成功
    • 客户端轮询appMaster查询进度时,发现作业执行成功,程序从waitForCompletion()退出
    • 作业的所有统计信息打印在控制台
    • appMaster及运行任务的容器,清理中间的输出结果
    • 作业信息被历史服务器保存,留待以后用户查询

yarn的生命周期

  • RM: Resource Manager
  • AM: Application Master
  • NM: Node Manager
  1. Client向RM提交应用,包括AM程序及启动AM的命令。
  2. RM为AM分配第一个容器,并与对应的NM通信,令其在容器上启动应用的AM。
  3. AM启动时向RM注册,允许Client向RM获取AM信息然后直接和AM通信。
  4. AM通过资源请求协议,为应用协商容器资源。
  5. 如容器分配成功,AM要求NM在容器中启动应用,应用启动后可以和AM独立通信。
  6. 应用程序在容器中执行,并向AM汇报。
  7. 在应用执行期间,Client和AM通信获取应用状态。
  8. 应用执行完成,AM向RM注销并关闭,释放资源。

Hadoop中的文件格式大致上分为面向行和面向列两类:

面向行:同一行的数据存储在一起,即连续存储。SequenceFile,MapFile,Avro Datafile。采用这种方式,如果只需要访问行的一小部分数据,亦需要将整行读入内存,推迟序列化一定程度上可以缓解这个问题,但是从磁盘读取整行数据的开销却无法避免。面向行的存储适合于整行数据需要同时处理的情况。

面向列:整个文件被切割为若干列数据,每一列数据一起存储。Parquet , RCFile,ORCFile。面向列的格式使得读取数据时,可以跳过不需要的列,适合于只处于行的一小部分字段的情况。但是这种格式的读写需要更多的内存空间,因为需要缓存行在内存中(为了获取多行中的某一列)。同时不适合流式写入,因为一旦写入失败,当前文件无法恢复,而面向行的数据在写入失败时可以重新同步到最后一个同步点,所以Flume采用的是面向行的存储格式。

默认情况下orc存储压缩率比parquet要高(压缩格式也可以更改,同样的压缩格式下,由于parquet格式数据schema更为复杂,所占空间略高。同snappy压缩格式,orc能达到1:3以上的压缩比,parquet则略低于1:3); 一般来说,orc读取效率比parquet要高(hadoop原生存储格式,对hive支持更友好); parquet支持嵌套数据格式,orc原生不支持嵌套数据类型(但可通过复杂数据类型如map<k,v>间接实现,此处情况即对应第二条中的“特例”,影响性能); parquet支持字段扩展,orc原生不支持字段扩展(但可手动重写读取方法实现,此处也对应第二条中的“特例”);

申请资源->启动appMaster->申请运行任务的container->分发Task->运行Task->Task结束->回收container