作者:张建
第一章 小文件过多带来的三个影响
Hive是一个建立在Apache Hadoop之上建立的一个数仓系统,Hive使用Hadoop中的HDFS组件存储数据文件。在使用hive时通常会遇到小文件问题,即存储在HDFS上的数据文件是明显小于HDFS文件块大小的。小文件问题通常会带来以下影响。
1.1 HDFS内存资源消耗过大,并限制了数据存储规模
在HDFS中,具体的文件保存在datanode节点中,在namenode节点中会有一个内存对象与之对应,用于存储文件的元信息,如位置、大小、分块等,每个对象大约占用150个字节。因此,过多的碎片文件会使得namenode消耗大量的内存资源用于管理文件元数据,性能也会因此受到影响,这样namenode的内存容量严重制约了集群的发展。
1.2 数据的访问更加耗时
处理小文件并非Hadoop的设计目标,HDFS的设计目标是流式访问大数据集。在HDFS中,每次读写文件都需要先从namenode获取文件元数据信息,然后与datanode建立连接。而访问大量的小文件会经常会需要大量的定位寻址操作,不断地在datanode间跳跃去检索小文件。这种访问方式严重影响性能。
1.3 数据运算的时间成本以及计算资源成本更高
在计算层面,小文件越多,意味着MapReduce执行任务时需要创建的map也会越多,这样,任务的启动与释放将耗费大量时间。同时,每一个map都会开启一个JVM虚拟机用于执行任务,带来的调度以及计算成本也更高了。
第二章 项目中的2个实际案例
前文描述了在以Hive为数仓的大数据系统中普遍存在大量小文件的现象以及该现象为大数据处理过程中带来的3个挑战。本章将介绍笔者近几年生涯中在项目上遇到的2个真实案例,更进一步向读者说明小文件的危害。
2.1 某医药行业客户项目内存压力问题
在某医药行业项目上,每天需要将数据从业务系统同步到数仓中。小数据量的表每天进行离线全量同步,大数据量表进行每天离线增量同步以及实时同步到kudu暂时缓存,然后再定时同步到hive中。但增量写入kudu经常出现任务失败的情况。
经排查,在CDH集群中,Kudu与HDFS namenode部署在同一台服务器上,并且写入hive的小文件过多,造成HDFS namenode占用了50%以上的物理内存用于存储文件元数据,导致有时候kudu写入数据时缓存区不足出现异常,从而导致任务失败。
针对这种情况,现场增加了一个spark任务定时调度执行,用于合并小文件,减轻了namenode对服务器造成的内存压力,从而解决了kudu任务异常的问题。
2.2 某零售行业客户项目数据查询变慢问题
在某零售行业客户项目中,发现有一个对增量hive表查询的任务速度执行越来越慢,测试发现,几千万数据统计耗时需要几分钟,排查发现由于每天定时从多个数据源增量同步数据到hive表,导致hive表中小文件堆积越来越多,SQL执行耗时越来越长。
第三章 小文件问题处理
前面讲到小文件过多现象以及危害,接下来将从问题产生的原因分析,并提供业界解决该问题的思路与方案。
3.1 小文件问题产生的原因
3.1.1 数据源本身包含大量的小文件
很多时候在Hive任务中,数据源就是Hive本身,当Hive中的表本身就包含大量小文件时,经过计算输出到目标表时,也将导致目标表中存在大量小文件。
3.1.2 reduce数量多导致生成的小文件增多
mapreduce任务中,reduce的数量与输出文件个数是对应的。在一些场景中,Hive对数据进行操作时,为了提升底层mapreduce程序的运算速度,也会增加reduce的数量,导致产生的结果文件数量增加。
3.1.3 使用动态分区导致小文件增多
很多场景需要将表根据数据内容进行动态分区,Hive在向分区表写入数据时,在mapreduce任务中每个map会为该map任务中的数据对应的动态分区生成文件,如果每个map中的数据对应的分区有10个,有4000个map,则这次写入会生成4000*10个文件。
3.2 小文件问题的2种解决思路
3.2.1 从源端限制小文件的产生
在源端控制小文件产生的个数,如使用可以追加写的存储格式,避免使用动态分区,执行mapreduce任务时减少reduce的数量。
这一类方案对应用场景有较高要求,如数仓建表需要兼顾到压缩性能和读取性能,这一类存储格式一般无法做到追加写入,而表是否需要动态分区和业务场景以及表数据量的大小有很大关系,减少reduce数量会使得任务执行效率变低。
3.2.2 对已产生的小文件进行处理
使用工具对已经生成的小文件进行归档打包,这样减小namenode内存压力的同时,仍然容许对文件进行透明的访问,但在需要覆盖写入时需要将文件解除归档。创建离线任务将小文件合并成大文件,这一类方案比较通用,缺点是合并时需要占用大量的计算资源。
3.3 大数据领域提供的解决方案
3.2节中介绍了大数据中处理该问题的2个思路,在大数据组件的发展过程中,很多组件已经运用上述思路对小文件问题进行了处理。
3.3.1 Hive的解决方案
Hive SQL提供了distribute by语法用于控制map端如何拆分数据给reduce,根据distribute by后面的列对应的reduce个数进行分发,默认采用hash算法,在使用上如配合分区表的分区列 insert select到结构与源表一样的目标表,可以在目标表对数据按分区进行压缩。
Hive还提供归档操作用于将小文件打包归档,如将分区表中一个分区的数据文件打包成一个归档文件,需要注意的是归档操作并不会压缩文件,并且读取归档数据时查询可能会变慢。
3.3.2 Spark的解决方案
Spark生成的文件数量直接取决于RDD里partition的数量和表分区数量。生成的文件数量一般是RDD分区数和表分区数的乘积。当任务并行度过高或者分区数目很大时,很容易产生很多的小文件。同样,也可以通过减少最后一个阶段RDD的分区数来达到减少生成文件数目的目的。
3.3.3 Flink的解决方案
Flink提供了小文件自动合并的功能,在同一个checkpoint周期的文件会自动合并。通过设置合理的checkpoint周期长度,可以减少文件生成的数量。
上述各大数据组件提供的方案都采用了第二种处理思路--即对已产生的小文件问题进行处理。第二种思路本质上是一种补救措施,无法从根本上限制小文件的产生,因此其效果会弱于第一种。如果文件合并速度比小文件的生产速度慢,将会对数据系统造成很大的影响。
第四章 DCT的小文件问题解决方案
上文提到了大数据组件中内置的一些解决小文件问题的方案,但受限于其本身的限制,这些大数据组件无法或很难采用第一种思路,这也导致在极端情况下其机制可能失效,因此我们需要一个按照第一种思路解决问题的方法。
DCT是一个用于异构数据源的数据同步工具,兼具实时CDC和离线同步两种同步方式。由于基于离线数据同步场景数据量较为固定,计算逻辑简单的特点,DCT选择了第一种解决思路--即减少小文件产生的方式,其本质上是一种事前预防措施。
DCT Hive离线写入组件从Hive获取表元数据,然后在本地构造数据文件后提交到HDFS。和直接使用Hive JDBC相比较,这种方式不依赖离线程序,写入速度不受Hive计算层的影响,而且单次任务内生成的数据文件大小及数量可控。这种方式可以从源端解决该问题。
本节描述了运用第一种思路的解决方案,但这类方案也有其局限性,只能解决单次离线数据同步带来的小文件问题,当遇到流式数据或者小批次数据同步任务时还是无法避免该问题。因此建议结合事前预防和事后补救两种思路一起实现方案,这样可以将小文件过多的影响尽量减小。